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