1/*jslint white: false, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true, newcap: true, immed: true */ 2/*global soundManager, window, document, navigator, setTimeout, attachEvent, Metadata, PP_CONFIG */ 3 4var pagePlayer = null; 5 6function PagePlayer() { 7 8 var self = this, 9 pl = this, 10 sm = soundManager, // soundManager instance 11 _event, 12 vuDataCanvas = null, 13 controlTemplate = null, 14 _head = document.getElementsByTagName('head')[0], 15 spectrumContainer = null, 16 // sniffing for favicon stuff, IE workarounds and touchy-feely devices 17 ua = navigator.userAgent, 18 supportsFavicon = (ua.match(/(opera|firefox)/i)), 19 isTouchDevice = (ua.match(/ipad|ipod|iphone/i)), 20 cleanup; 21 22 // configuration options 23 // note that if Flash 9 is required, you must set soundManager.flashVersion = 9 in your script before this point. 24 25 this.config = { 26 usePeakData: false, // [Flash 9 only]: show peak data 27 useWaveformData: false, // [Flash 9 only]: enable sound spectrum (raw waveform data) - WARNING: CPU-INTENSIVE: may set CPUs on fire. 28 useEQData: false, // [Flash 9 only]: enable sound EQ (frequency spectrum data) - WARNING: Also CPU-intensive. 29 fillGraph: false, // [Flash 9 only]: draw full lines instead of only top (peak) spectrum points 30 allowRightClick: true, // let users right-click MP3 links ("save as...", etc.) or discourage (can't prevent.) 31 useThrottling: true, // try to rate-limit potentially-expensive calls (eg. dragging position around) 32 autoStart: false, // begin playing first sound when page loads 33 playNext: true, // stop after one sound, or play through list until end 34 updatePageTitle: true, // change the page title while playing sounds 35 emptyTime: '-:--', // null/undefined timer values (before data is available) 36 useFavIcon: true // try to show peakData in address bar (Firefox + Opera) - may be too CPU heavy 37 }; 38 39 this.css = { // CSS class names appended to link during various states
40 sDefault: 'sm2_link', // default state 41 sLoading: 'sm2_loading', 42 sPlaying: 'sm2_playing', 43 sPaused: 'sm2_paused' 44 }; 45 46 this.sounds = []; 47 this.soundsByObject = []; 48 this.lastSound = null; 49 this.soundCount = 0; 50 this.strings = []; 51 this.dragActive = false; 52 this.dragExec = new Date(); 53 this.dragTimer = null; 54 this.pageTitle = document.title; 55 this.lastWPExec = new Date(); 56 this.lastWLExec = new Date(); 57 this.vuMeterData = []; 58 this.oControls = null; 59 60 this._mergeObjects = function(oMain,oAdd) { 61 // non-destructive merge 62 var o1 = {}, o2, i, o; // clone o1 63 for (i in oMain) { 64 if (oMain.hasOwnProperty(i)) { 65 o1[i] = oMain[i]; 66 } 67 } 68 o2 = (typeof oAdd === 'undefined'?{}:oAdd); 69 for (o in o2) { 70 if (typeof o1[o] === 'undefined') { 71 o1[o] = o2[o]; 72 } 73 } 74 return o1; 75 }; 76 77 _event = (function() { 78 79 var old = (window.attachEvent && !window.addEventListener), 80 _slice = Array.prototype.slice, 81 evt = { 82 add: (old?'attachEvent':'addEventListener'), 83 remove: (old?'detachEvent':'removeEventListener') 84 }; 85 86 function getArgs(oArgs) { 87 var args = _slice.call(oArgs), len = args.length; 88 if (old) { 89 args[1] = 'on' + args[1]; // prefix 90 if (len > 3) { 91 args.pop(); // no capture 92 } 93 } else if (len === 3) { 94 args.push(false); 95 } 96 return args; 97 } 98 99 function apply(args, sType) { 100 var element = args.shift(), 101 method = [evt[sType]]; 102 if (old) { 103 element[method](args[0], args[1]); 104 } else { 105 element[method].apply(element, args); 106 } 107 } 108 109 function add() { 110 apply(getArgs(arguments), 'add'); 111 } 112 113 function remove() { 114 apply(getArgs(arguments), 'remove'); 115 } 116 117 return { 118 'add': add, 119 'remove': remove 120 }; 121 122 }()); 123 124 // event + DOM utilities 125 126 this.hasClass = function(o, cStr) { 127 return (typeof(o.className)!=='undefined'?new RegExp('(^|\\s)'+cStr+'(\\s|$)').test(o.className):false); 128 }; 129 130 this.addClass = function(o, cStr) { 131 if (!o || !cStr || self.hasClass(o,cStr)) { 132 return false; // safety net 133 } 134 o.className = (o.className?o.className+' ':'')+cStr; 135 }; 136 137 this.removeClass = function(o, cStr) { 138 if (!o || !cStr || !self.hasClass(o,cStr)) { 139 return false; 140 } 141 o.className = o.className.replace(new RegExp('( '+cStr+')|('+cStr+')','g'),''); 142 }; 143 144 this.select = function(className, oParent) { 145 var result = self.getByClassName(className, 'div', oParent||null); 146 return (result ? result[0] : null); 147 }; 148 149 this.getByClassName = (document.querySelectorAll ? function(className, tagNames, oParent) { // tagNames: string or ['div', 'p'] etc. 150 151 var pattern = ('.'+className), qs; 152 if (tagNames) { 153 tagNames = tagNames.split(' '); 154 } 155 qs = (tagNames.length > 1 ? tagNames.join(pattern+', ') : tagNames[0]+pattern); 156 return (oParent?oParent:document).querySelectorAll(qs); 157 158 } : function(className, tagNames, oParent) { 159 160 var node = (oParent?oParent:document), matches = [], i, j, nodes = []; 161 if (tagNames) { 162 tagNames = tagNames.split(' '); 163 } 164 if (tagNames instanceof Array) { 165 for (i=tagNames.length; i--;) { 166 if (!nodes || !nodes[tagNames[i]]) { 167 nodes[tagNames[i]] = node.getElementsByTagName(tagNames[i]); 168 } 169 } 170 for (i=tagNames.length; i--;) { 171 for (j=nodes[tagNames[i]].length; j--;) { 172 if (self.hasClass(nodes[tagNames[i]][j], className)) { 173 matches.push(nodes[tagNames[i]][j]); 174 } 175 } 176 } 177 } else { 178 nodes = node.all||node.getElementsByTagName('*'); 179 for (i=0, j=nodes.length; i<j; i++) { 180 if (self.hasClass(nodes[i],className)) { 181 matches.push(nodes[i]); 182 } 183 } 184 } 185 return matches; 186 187 }); 188 189 this.isChildOfClass = function(oChild, oClass) { 190 if (!oChild || !oClass) { 191 return false; 192 }
193 while (oChild.parentNode && !self.hasClass(oChild,oClass)) { 194 oChild = oChild.parentNode; 195 } 196 return (self.hasClass(oChild,oClass)); 197 }; 198 199 this.getParentByNodeName = function(oChild, sParentNodeName) { 200 if (!oChild || !sParentNodeName) { 201 return false; 202 } 203 sParentNodeName = sParentNodeName.toLowerCase(); 204 while (oChild.parentNode && sParentNodeName !== oChild.parentNode.nodeName.toLowerCase()) { 205 oChild = oChild.parentNode; 206 } 207 return (oChild.parentNode && sParentNodeName === oChild.parentNode.nodeName.toLowerCase()?oChild.parentNode:null); 208 }; 209 210 this.getOffX = function(o) { 211 // http://www.xs4all.nl/~ppk/js/findpos.html 212 var curleft = 0; 213 if (o.offsetParent) { 214 while (o.offsetParent) { 215 curleft += o.offsetLeft; 216 o = o.offsetParent; 217 } 218 } 219 else if (o.x) { 220 curleft += o.x; 221 } 222 return curleft; 223 }; 224 225 this.getTime = function(nMSec, bAsString) { 226 // convert milliseconds to mm:ss, return as object literal or string 227 var nSec = Math.floor(nMSec/1000), 228 min = Math.floor(nSec/60), 229 sec = nSec-(min*60); 230 // if (min === 0 && sec === 0) return null; // return 0:00 as null 231 return (bAsString?(min+':'+(sec<10?'0'+sec:sec)):{'min':min,'sec':sec}); 232 }; 233 234 this.getSoundByObject = function(o) { 235 return (typeof self.soundsByObject[o.id] !== 'undefined'?self.soundsByObject[o.id]:null); 236 }; 237 238 this.getPreviousItem = function(o) { 239 // given <li> playlist item, find previous <li> and then <a> 240 if (o.previousElementSibling) { 241 o = o.previousElementSibling; 242 } else { 243 o = o.previousSibling; // move from original node.. 244 while (o && o.previousSibling && o.previousSibling.nodeType !== 1) { 245 o = o.previousSibling; 246 } 247 } 248 if (o.nodeName.toLowerCase() !== 'li') { 249 return null; 250 } else { 251 return o.getElementsByTagName('a')[0]; 252 } 253 }; 254 255 this.playPrevious = function(oSound) { 256 if (!oSound) { 257 oSound = self.lastSound; 258 } 259 if (!oSound) { 260 return false; 261 } 262 var previousItem = self.getPreviousItem(oSound._data.oLI); 263 if (previousItem) { 264 pl.handleClick({target:previousItem}); // fake a click event - aren't we sneaky. ;) 265 } 266 return previousItem; 267 }; 268 269 this.getNextItem = function(o) { 270 // given <li> playlist item, find next <li> and then <a> 271 if (o.nextElementSibling) { 272 o = o.nextElementSibling; 273 } else { 274 o = o.nextSibling; // move from original node.. 275 while (o && o.nextSibling && o.nextSibling.nodeType !== 1) { 276 o = o.nextSibling; 277 } 278 } 279 if (o.nodeName.toLowerCase() !== 'li') { 280 return null; 281 } else { 282 return o.getElementsByTagName('a')[0]; 283 } 284 }; 285 286 this.playNext = function(oSound) { 287 if (!oSound) { 288 oSound = self.lastSound; 289 } 290 if (!oSound) { 291 return false; 292 } 293 var nextItem = self.getNextItem(oSound._data.oLI); 294 if (nextItem) { 295 pl.handleClick({target:nextItem}); // fake a click event - aren't we sneaky. ;) 296 } 297 return nextItem; 298 }; 299 300 this.setPageTitle = function(sTitle) { 301 if (!self.config.updatePageTitle) { 302 return false; 303 } 304 try { 305 document.title = (sTitle?sTitle+' - ':'')+self.pageTitle; 306 } catch(e) { 307 // oh well 308 self.setPageTitle = function() { 309 return false; 310 }; 311 } 312 }; 313 314 this.events = { 315 316 // handlers for sound events as they're started/stopped/played 317 318 play: function() { 319 pl.removeClass(this._data.oLI,this._data.className); 320 this._data.className = pl.css.sPlaying; 321 pl.addClass(this._data.oLI,this._data.className); 322 self.setPageTitle(this._data.originalTitle); 323 }, 324 325 stop: function() { 326 pl.removeClass(this._data.oLI,this._data.className); 327 this._data.className = ''; 328 this._data.oPosition.style.width = '0px'; 329 self.setPageTitle(); 330 self.resetPageIcon(); 331 }, 332 333 pause: function() { 334 if (pl.dragActive) { 335 return false; 336 } 337 pl.removeClass(this._data.oLI,this._data.className); 338 this._data.className = pl.css.sPaused; 339 pl.addClass(this._data.oLI,this._data.className); 340 self.setPageTitle(); 341 self.resetPageIcon(); 342 }, 343 344 resume: function() { 345 if (pl.dragActive) { 346 return false;
347 } 348 pl.removeClass(this._data.oLI,this._data.className); 349 this._data.className = pl.css.sPlaying; 350 pl.addClass(this._data.oLI,this._data.className); 351 }, 352 353 finish: function() { 354 pl.removeClass(this._data.oLI,this._data.className); 355 this._data.className = ''; 356 this._data.oPosition.style.width = '0px'; 357 // play next if applicable 358 if (self.config.playNext) { 359 pl.playNext(this); 360 } else { 361 self.setPageTitle(); 362 self.resetPageIcon(); 363 } 364 }, 365 366 whileloading: function() { 367 function doWork() { 368 this._data.oLoading.style.width = (((this.bytesLoaded/this.bytesTotal)*100)+'%'); // theoretically, this should work. 369 if (!this._data.didRefresh && this._data.metadata) { 370 this._data.didRefresh = true; 371 this._data.metadata.refresh(); 372 } 373 } 374 if (!pl.config.useThrottling) { 375 doWork.apply(this); 376 } else { 377 var d = new Date(); 378 if (d && d-self.lastWLExec>30 || this.bytesLoaded === this.bytesTotal) { 379 doWork.apply(this); 380 self.lastWLExec = d; 381 } 382 } 383 384 }, 385 386 onload: function() { 387 if (!this.loaded) { 388 var oTemp = this._data.oLI.getElementsByTagName('a')[0], 389 oString = oTemp.innerHTML, 390 oThis = this; 391 oTemp.innerHTML = oString+' <span style="font-size:0.5em"> | Load failed, d\'oh! '+(sm.sandbox.noRemote?' Possible cause: Flash sandbox is denying remote URL access.':(sm.sandbox.noLocal?'Flash denying local filesystem access':'404?'))+'</span>'; 392 setTimeout(function(){ 393 oTemp.innerHTML = oString; 394 // pl.events.finish.apply(oThis); // load next 395 },5000); 396 } else { 397 if (this._data.metadata) { 398 this._data.metadata.refresh(); 399 } 400 } 401 }, 402 403 whileplaying: function() { 404 var d = null; 405 if (pl.dragActive || !pl.config.useThrottling) { 406 self.updateTime.apply(this); 407 if (sm.flashVersion >= 9) { 408 if (pl.config.usePeakData && this.instanceOptions.usePeakData) { 409 self.updatePeaks.apply(this); 410 } 411 if (pl.config.useWaveformData && this.instanceOptions.useWaveformData || pl.config.useEQData && this.instanceOptions.useEQData) { 412 self.updateGraph.apply(this); 413 } 414 } 415 if (this._data.metadata) { 416 d = new Date(); 417 if (d && d-self.lastWPExec>500) { 418 this._data.metadata.refreshMetadata(this); 419 self.lastWPExec = d; 420 } 421 } 422 this._data.oPosition.style.width = (((this.position/self.getDurationEstimate(this))*100)+'%'); 423 } else { 424 d = new Date(); 425 if (d-self.lastWPExec>30) { 426 self.updateTime.apply(this); 427 if (sm.flashVersion >= 9) { 428 if (pl.config.usePeakData && this.instanceOptions.usePeakData) { 429 self.updatePeaks.apply(this); 430 } 431 if (pl.config.useWaveformData && this.instanceOptions.useWaveformData || pl.config.useEQData && this.instanceOptions.useEQData) { 432 self.updateGraph.apply(this); 433 } 434 } 435 if (this._data.metadata) { 436 this._data.metadata.refreshMetadata(this); 437 } 438 this._data.oPosition.style.width = (((this.position/self.getDurationEstimate(this))*100)+'%'); 439 self.lastWPExec = d; 440 } 441 } 442 } 443 444 }; // events{} 445 446 this.setPageIcon = function(sDataURL) { 447 if (!self.config.useFavIcon || !self.config.usePeakData || !sDataURL) { 448 return false; 449 } 450 var link = document.getElementById('sm2-favicon'); 451 if (link) { 452 _head.removeChild(link); 453 link = null; 454 } 455 if (!link) { 456 link = document.createElement('link'); 457 link.id = 'sm2-favicon'; 458 link.rel = 'shortcut icon'; 459 link.type = 'image/png'; 460 link.href = sDataURL; 461 document.getElementsByTagName('head')[0].appendChild(link); 462 } 463 }; 464 465 this.resetPageIcon = function() { 466 if (!self.config.useFavIcon) { 467 return false; 468 } 469 var link = document.getElementById('favicon'); 470 if (link) { 471 link.href = '/favicon.ico'; 472 } 473 }; 474 475 this.updatePeaks = function() { 476 var o = this._data.oPeak, 477 oSpan = o.getElementsByTagName('span'); 478 oSpan[0].style.marginTop = (13-(Math.floor(15*this.peakData.left))+'px'); 479 oSpan[1].style.marginTop = (13-(Math.floor(15*this.peakData.right))+'px'); 480 if (sm.flashVersion > 8 && self.config.useFavIcon && self.config.usePeakData) { 481 self.setPageIcon(self.vuMeterData[parseInt(16*this.peakData.left,10)][parseInt(16*this.peakData.right,10)]); 482 } 483 }; 484 485 this.updateGraph = function() { 486 if (pl.config.flashVersion < 9 || (!pl.config.useWaveformData && !pl.config.useEQData)) { 487 return false;
488 } 489 var sbC = this._data.oGraph.getElementsByTagName('div'), 490 scale, i, offset; 491 if (pl.config.useWaveformData) { 492 // raw waveform 493 scale = 8; // Y axis (+/- this distance from 0) 494 for (i=255; i--;) { 495 sbC[255-i].style.marginTop = (1+scale+Math.ceil(this.waveformData.left[i]*-scale))+'px'; 496 } 497 } else { 498 // eq spectrum 499 offset = 9; 500 for (i=255; i--;) { 501 sbC[255-i].style.marginTop = ((offset*2)-1+Math.ceil(this.eqData[i]*-offset))+'px'; 502 } 503 } 504 }; 505 506 this.resetGraph = function() { 507 if (!pl.config.useEQData || pl.config.flashVersion<9) { 508 return false; 509 } 510 var sbC = this._data.oGraph.getElementsByTagName('div'), 511 scale = (!pl.config.useEQData?'9px':'17px'), 512 nHeight = (!pl.config.fillGraph?'1px':'32px'), 513 i; 514 for (i=255; i--;) { 515 sbC[255-i].style.marginTop = scale; // EQ scale 516 sbC[255-i].style.height = nHeight; 517 } 518 }; 519 520 this.updateTime = function() { 521 var str = self.strings.timing.replace('%s1',self.getTime(this.position,true)); 522 str = str.replace('%s2',self.getTime(self.getDurationEstimate(this),true)); 523 this._data.oTiming.innerHTML = str; 524 }; 525 526 this.getTheDamnTarget = function(e) { 527 return (e.target||(window.event?window.event.srcElement:null)); 528 }; 529 530 this.withinStatusBar = function(o) { 531 return (self.isChildOfClass(o,'controls')); 532 }; 533 534 this.handleClick = function(e) { 535 536 // a sound (or something) was clicked - determine what and handle appropriately 537 538 if (e.button === 2) { 539 if (!pl.config.allowRightClick) { 540 pl.stopEvent(e); 541 } 542 return pl.config.allowRightClick; // ignore right-clicks 543 } 544 var o = self.getTheDamnTarget(e), 545 sURL, soundURL, thisSound, oControls, oLI, str; 546 if (!o) { 547 return true; 548 } 549 if (self.dragActive) { 550 self.stopDrag(); // to be safe 551 } 552 if (self.withinStatusBar(o)) { 553 // self.handleStatusClick(e); 554 return false; 555 } 556 if (o.nodeName.toLowerCase() !== 'a') { 557 o = self.getParentByNodeName(o,'a'); 558 } 559 if (!o) { 560 // not a link 561 return true; 562 } 563 564 // OK, we're dealing with a link 565 566 sURL = o.getAttribute('href'); 567 568 if (!o.href || (!sm.canPlayLink(o) && !self.hasClass(o,'playable')) || self.hasClass(o,'exclude')) { 569 570 // do nothing, don't return anything. 571 return true; 572 573 } else { 574 575 // we have something we're interested in. 576 577 // find and init parent UL, if need be 578 self.initUL(self.getParentByNodeName(o, 'ul')); 579 580 // and decorate the link too, if needed 581 self.initItem(o); 582 583 soundURL = o.href; 584 thisSound = self.getSoundByObject(o); 585 586 if (thisSound) { 587 588 // sound already exists 589 self.setPageTitle(thisSound._data.originalTitle); 590 if (thisSound === self.lastSound) { 591 // ..and was playing (or paused) and isn't in an error state 592 if (thisSound.readyState !== 2) { 593 if (thisSound.playState !== 1) { 594 // not yet playing 595 thisSound.play(); 596 } else { 597 thisSound.togglePause(); 598 } 599 } else { 600 sm._writeDebug('Warning: sound failed to load (security restrictions, 404 or bad format)',2); 601 } 602 } else { 603 // ..different sound 604 if (self.lastSound) { 605 self.stopSound(self.lastSound); 606 } 607 if (spectrumContainer) { 608 thisSound._data.oTimingBox.appendChild(spectrumContainer); 609 } 610 thisSound.togglePause(); // start playing current 611 } 612 613 } else { 614 615 // create sound 616 thisSound = sm.createSound({ 617 id:o.id, 618 url:decodeURI(soundURL), 619 onplay:self.events.play, 620 onstop:self.events.stop, 621 onpause:self.events.pause, 622 onresume:self.events.resume, 623 onfinish:self.events.finish, 624 whileloading:self.events.whileloading, 625 whileplaying:self.events.whileplaying, 626 onmetadata:self.events.metadata, 627 onload:self.events.onload 628 }); 629 630 // append control template 631 oControls = self.oControls.cloneNode(true); 632 oLI = o.parentNode;
633 oLI.appendChild(oControls); 634 if (spectrumContainer) { 635 oLI.appendChild(spectrumContainer); 636 } 637 self.soundsByObject[o.id] = thisSound; 638 639 // tack on some custom data 640 thisSound._data = { 641 oLink: o, // DOM reference within SM2 object event handlers 642 oLI: oLI, 643 oControls: self.select('controls',oLI), 644 oStatus: self.select('statusbar',oLI), 645 oLoading: self.select('loading',oLI), 646 oPosition: self.select('position',oLI), 647 oTimingBox: self.select('timing',oLI), 648 oTiming: self.select('timing',oLI).getElementsByTagName('div')[0], 649 oPeak: self.select('peak',oLI), 650 oGraph: self.select('spectrum-box',oLI), 651 className: self.css.sPlaying, 652 originalTitle: o.innerHTML, 653 metadata: null 654 }; 655 656 if (spectrumContainer) { 657 thisSound._data.oTimingBox.appendChild(spectrumContainer); 658 } 659 660 // "Metadata" 661 if (thisSound._data.oLI.getElementsByTagName('ul').length) { 662 thisSound._data.metadata = new Metadata(thisSound); 663 } 664 665 // set initial timer stuff (before loading) 666 str = self.strings.timing.replace('%s1',self.config.emptyTime); 667 str = str.replace('%s2',self.config.emptyTime); 668 thisSound._data.oTiming.innerHTML = str; 669 self.sounds.push(thisSound); 670 if (self.lastSound) { 671 self.stopSound(self.lastSound); 672 } 673 self.resetGraph.apply(thisSound); 674 thisSound.play(); 675 676 } 677 678 self.lastSound = thisSound; // reference for next call 679 return self.stopEvent(e); 680 681 } 682 683 }; 684 685 this.handleMouseDown = function(e) { 686 // a sound link was clicked 687 if (isTouchDevice && e.touches) { 688 e = e.touches[0]; 689 } 690 if (e.button === 2) { 691 if (!pl.config.allowRightClick) { 692 pl.stopEvent(e); 693 } 694 return pl.config.allowRightClick; // ignore right-clicks 695 } 696 var o = self.getTheDamnTarget(e); 697 if (!o) { 698 return true; 699 } 700 if (!self.withinStatusBar(o)) { 701 return true; 702 } 703 self.dragActive = true; 704 self.lastSound.pause(); 705 self.setPosition(e); 706 if (!isTouchDevice) { 707 _event.add(document,'mousemove',self.handleMouseMove); 708 } else { 709 _event.add(document,'touchmove',self.handleMouseMove); 710 } 711 self.addClass(self.lastSound._data.oControls,'dragging'); 712 return self.stopEvent(e); 713 }; 714 715 this.handleMouseMove = function(e) { 716 if (isTouchDevice && e.touches) { 717 e = e.touches[0]; 718 } 719 // set position accordingly 720 if (self.dragActive) { 721 if (self.config.useThrottling) { 722 // be nice to CPU/externalInterface 723 var d = new Date(); 724 if (d-self.dragExec>20) { 725 self.setPosition(e); 726 } else { 727 window.clearTimeout(self.dragTimer); 728 self.dragTimer = window.setTimeout(function(){self.setPosition(e);},20); 729 } 730 self.dragExec = d; 731 } else { 732 // oh the hell with it 733 self.setPosition(e); 734 } 735 } else { 736 self.stopDrag(); 737 } 738 e.stopPropagation = true; 739 return false; 740 }; 741 742 this.stopDrag = function(e) { 743 if (self.dragActive) { 744 self.removeClass(self.lastSound._data.oControls,'dragging'); 745 if (!isTouchDevice) { 746 _event.remove(document,'mousemove',self.handleMouseMove); 747 } else { 748 _event.remove(document,'touchmove',self.handleMouseMove); 749 } 750 if (!pl.hasClass(self.lastSound._data.oLI,self.css.sPaused)) { 751 self.lastSound.resume(); 752 } 753 self.dragActive = false; 754 return self.stopEvent(e); 755 } 756 }; 757 758 this.handleStatusClick = function(e) { 759 self.setPosition(e); 760 if (!pl.hasClass(self.lastSound._data.oLI,self.css.sPaused)) { 761 self.resume(); 762 } 763 return self.stopEvent(e); 764 }; 765 766 this.stopEvent = function(e) { 767 if (typeof e !== 'undefined') { 768 if (typeof e.preventDefault !== 'undefined') { 769 e.preventDefault(); 770 } else { 771 e.stopPropagation = true; 772 e.returnValue = false; 773 } 774 } 775 return false;
776 }; 777 778 this.setPosition = function(e) { 779 // called from slider control 780 var oThis = self.getTheDamnTarget(e), 781 x, oControl, oSound, nMsecOffset; 782 if (!oThis) { 783 return true; 784 } 785 oControl = oThis; 786 while (!self.hasClass(oControl,'controls') && oControl.parentNode) { 787 oControl = oControl.parentNode; 788 } 789 oSound = self.lastSound; 790 x = parseInt(e.clientX,10); 791 // play sound at this position 792 nMsecOffset = Math.floor((x-self.getOffX(oControl)-4)/(oControl.offsetWidth)*self.getDurationEstimate(oSound)); 793 if (!isNaN(nMsecOffset)) { 794 nMsecOffset = Math.min(nMsecOffset,oSound.duration); 795 } 796 if (!isNaN(nMsecOffset)) { 797 oSound.setPosition(nMsecOffset); 798 } 799 }; 800 801 this.stopSound = function(oSound) { 802 sm._writeDebug('stopping sound: '+oSound.sID); 803 sm.stop(oSound.sID); 804 if (!isTouchDevice) { // iOS 4.2+ security blocks onfinish() -> playNext() if we set a .src in-between(?) 805 sm.unload(oSound.sID); 806 } 807 }; 808 809 this.getDurationEstimate = function(oSound) { 810 if (oSound.instanceOptions.isMovieStar) { 811 return (oSound.duration); 812 } else { 813 return (!oSound._data.metadata || !oSound._data.metadata.data.givenDuration ? (oSound.durationEstimate||0) : oSound._data.metadata.data.givenDuration); 814 } 815 }; 816 817 this.createVUData = function() { 818 819 var i=0, j=0, 820 canvas = vuDataCanvas.getContext('2d'), 821 vuGrad = canvas.createLinearGradient(0, 16, 0, 0), 822 bgGrad, outline; 823 824 vuGrad.addColorStop(0,'rgb(0,192,0)'); 825 vuGrad.addColorStop(0.30,'rgb(0,255,0)'); 826 vuGrad.addColorStop(0.625,'rgb(255,255,0)'); 827 vuGrad.addColorStop(0.85,'rgb(255,0,0)'); 828 bgGrad = canvas.createLinearGradient(0, 16, 0, 0); 829 outline = 'rgba(0,0,0,0.2)'; 830 bgGrad.addColorStop(0,outline); 831 bgGrad.addColorStop(1,'rgba(0,0,0,0.5)'); 832 for (i=0; i<16; i++) { 833 self.vuMeterData[i] = []; 834 } 835 for (i=0; i<16; i++) { 836 for (j=0; j<16; j++) { 837 // reset/erase canvas 838 vuDataCanvas.setAttribute('width',16); 839 vuDataCanvas.setAttribute('height',16); 840 // draw new stuffs 841 canvas.fillStyle = bgGrad; 842 canvas.fillRect(0,0,7,15); 843 canvas.fillRect(8,0,7,15); 844 /* 845 // shadow 846 canvas.fillStyle = 'rgba(0,0,0,0.1)'; 847 canvas.fillRect(1,15-i,7,17-(17-i)); 848 canvas.fillRect(9,15-j,7,17-(17-j)); 849 */ 850 canvas.fillStyle = vuGrad; 851 canvas.fillRect(0,15-i,7,16-(16-i)); 852 canvas.fillRect(8,15-j,7,16-(16-j)); 853 // and now, clear out some bits. 854 canvas.clearRect(0,3,16,1); 855 canvas.clearRect(0,7,16,1); 856 canvas.clearRect(0,11,16,1); 857 self.vuMeterData[i][j] = vuDataCanvas.toDataURL('image/png'); 858 // for debugging VU images 859 /* 860 var o = document.createElement('img'); 861 o.style.marginRight = '5px'; 862 o.src = self.vuMeterData[i][j]; 863 document.documentElement.appendChild(o); 864 */ 865 } 866 } 867 868 }; 869 870 this.testCanvas = function() { 871 // canvas + toDataURL(); 872 var c = document.createElement('canvas'), 873 ctx = null, ok; 874 if (!c || typeof c.getContext === 'undefined') { 875 return null; 876 } 877 ctx = c.getContext('2d'); 878 if (!ctx || typeof c.toDataURL !== 'function') { 879 return null; 880 } 881 // just in case.. 882 try { 883 ok = c.toDataURL('image/png'); 884 } catch(e) { 885 // no canvas or no toDataURL() 886 return null; 887 } 888 // assume we're all good. 889 return c; 890 }; 891 892 this.initItem = function(oNode) { 893 if (!oNode.id) { 894 oNode.id = 'pagePlayerMP3Sound'+(self.soundCount++); 895 } 896 self.addClass(oNode,self.css.sDefault); // add default CSS decoration 897 }; 898 899 this.initUL = function(oULNode) { 900 // set up graph box stuffs 901 if (sm.flashVersion >= 9) { 902 self.addClass(oULNode,self.cssBase); 903 } 904 }; 905 906 this.init = function(oConfig) { 907 908 if (oConfig) { 909 // allow overriding via arguments object 910 sm._writeDebug('pagePlayer.init(): Using custom configuration'); 911 this.config = this._mergeObjects(oConfig,this.config); 912 } else { 913 sm._writeDebug('pagePlayer.init(): Using default configuration'); 914 } 915 916 var i, spectrumBox, sbC, oF, oClone, oTiming; 917 918 // apply externally-defined override, if applicable 919 this.cssBase = []; // optional features added to ul.playlist 920 921 // apply some items to SM2 922 sm.useFlashBlock = true; 923 924 if (sm.flashVersion >= 9) { 925 926 sm.defaultOptions.usePeakData = this.config.usePeakData; 927 sm.defaultOptions.useWaveformData = this.config.useWaveformData; 928 sm.defaultOptions.useEQData = this.config.useEQData; 929 930 if (this.config.usePeakData) { 931 this.cssBase.push('use-peak'); 932 } 933 934 if (this.config.useWaveformData || this.config.useEQData) { 935 this.cssBase.push('use-spectrum'); 936 } 937 938 this.cssBase = this.cssBase.join(' '); 939 940 if (this.config.useFavIcon) { 941 vuDataCanvas = self.testCanvas(); 942 if (vuDataCanvas && supportsFavicon) { 943 // these browsers support dynamically-updating the favicon 944 self.createVUData(); 945 } else { 946 // browser doesn't support doing this 947 this.config.useFavIcon = false;
948 } 949 } 950 951 } else if (this.config.usePeakData || this.config.useWaveformData || this.config.useEQData) { 952 953 sm._writeDebug('Page player: Note: soundManager.flashVersion = 9 is required for peak/waveform/EQ features.'); 954 955 } 956 957 controlTemplate = document.createElement('div'); 958 959 controlTemplate.innerHTML = [ 960 961 962 ' <div class="controls">', 963 ' <div class="statusbar">', 964 ' <div class="loading"></div>', 965 ' <div class="position"></div>', 966 ' </div>', 967 ' </div>', 968 969 ' <div class="timing">', 970 ' <div id="sm2_timing" class="timing-data">', 971 ' <span class="sm2_position">%s1</span><span class="sm2_total">%s2</span>', 972 ' </div>', 973 '<p class="URL"></p>', 974 ' </div>', 975 976 ' <div class="peak">', 977 ' <div class="peak-box"><span class="l"></span><span class="r"></span></div>', 978 ' </div>', 979 980 ' <div class="spectrum-container">', 981 ' <div class="spectrum-box">', 982 ' <div class="spectrum"></div>', 983 ' </div>', 984 ' </div>' 985 986 987 ].join('\n'); 988 989 if (sm.flashVersion >= 9) { 990 991 // create the spectrum box ish 992 spectrumContainer = self.select('spectrum-container',controlTemplate); 993 994 // take out of template, too 995 spectrumContainer = controlTemplate.removeChild(spectrumContainer); 996 997 spectrumBox = self.select('spectrum-box',spectrumContainer); 998 999 sbC = spectrumBox.getElementsByTagName('div')[0]; 1000 oF = document.createDocumentFragment(); 1001 oClone = null; 1002 for (i=256; i--;) { 1003 oClone = sbC.cloneNode(false); 1004 oClone.style.left = (i)+'px'; 1005 oF.appendChild(oClone); 1006 } 1007 spectrumBox.removeChild(sbC); 1008 spectrumBox.appendChild(oF); 1009 1010 } else { 1011 1012 // flash 8-only, take out the spectrum container and peak elements 1013 controlTemplate.removeChild(self.select('spectrum-container',controlTemplate)); 1014 controlTemplate.removeChild(self.select('peak',controlTemplate)); 1015 1016 } 1017 1018 self.oControls = controlTemplate.cloneNode(true); 1019 1020 oTiming = self.select('timing-data',controlTemplate); 1021 self.strings.timing = oTiming.innerHTML; 1022 oTiming.innerHTML = ''; 1023 oTiming.id = ''; 1024 1025 function doEvents(action) { // action: add / remove 1026 1027 _event[action](document,'click',self.handleClick); 1028 1029 if (!isTouchDevice) { 1030 _event[action](document,'mousedown',self.handleMouseDown); 1031 _event[action](document,'mouseup',self.stopDrag); 1032 } else { 1033 _event[action](document,'touchstart',self.handleMouseDown); 1034 _event[action](document,'touchend',self.stopDrag); 1035 } 1036 1037 _event[action](window, 'unload', cleanup); 1038 1039 } 1040 1041 cleanup = function() { 1042 doEvents('remove'); 1043 }; 1044 1045 doEvents('add'); 1046 1047 sm._writeDebug('pagePlayer.init(): Ready',1); 1048 1049 if (self.config.autoStart) { 1050 // Find file by mp3 1051 for (i=0, j=oLinks.length; i<j; i++) { 1052 if (self.classContains(oLinks[i],self.css.sDefault) && !self.classContains(oLinks[i],self.excludeClass)) { 1053 // self.addClass(oLinks[i],self.css.sDefault); // add default CSS decoration - good if you're lazy and want ALL MP3/playable links to do this 1054 self.links[foundItems] = (oLinks[i]); 1055 self.indexByURL[oLinks[i].href] = foundItems; // hack for indexing 1056 foundItems++; 1057 } 1058 } 1059 } 1060 1061 }; 1062 1063} 1064 1065soundManager.useFlashBlock = true; 1066 1067soundManager.onready(function() { 1068 pagePlayer = new PagePlayer(); 1069 pagePlayer.init(typeof PP_CONFIG !== 'undefined' ? PP_CONFIG : null); 1070});
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.