PageSourceSearch

https://popstache.com/wp-content/themes/popstache-v1.1.8/js/player/page-player.js?ver=1

js popstache.com collected 2026-10-02 07:19:13 UTC 32,350 bytes, 1,070 lines download raw bytes

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.