Index: gs3-extensions/audioDB/trunk/src/interface/script/page-player.js.orig
===================================================================
--- gs3-extensions/audioDB/trunk/src/interface/script/page-player.js.orig	(revision 30440)
+++ gs3-extensions/audioDB/trunk/src/interface/script/page-player.js.orig	(revision 30440)
@@ -0,0 +1,1083 @@
+/*
+
+  SoundManager 2 Demo: "Page as playlist" UI
+  ----------------------------------------------
+  http://schillmania.com/projects/soundmanager2/
+
+  An example of a Muxtape.com-style UI, where an
+  unordered list of MP3 links becomes a playlist
+
+  Flash 9 "MovieStar" edition supports MPEG4
+  audio as well.
+
+  Requires SoundManager 2 Javascript API.
+
+*/
+
+/*jslint white: false, onevar: true, undef: true, nomen: false, eqeqeq: true, plusplus: false, bitwise: true, newcap: true, immed: true */
+/*global soundManager, window, document, navigator, setTimeout, attachEvent, Metadata, PP_CONFIG */
+
+var pagePlayer = null;
+
+function PagePlayer() {
+
+  var self = this,
+      pl = this,
+      sm = soundManager, // soundManager instance
+      _event,
+      vuDataCanvas = null,
+      controlTemplate = null,
+      _head = document.getElementsByTagName('head')[0],
+      spectrumContainer = null,
+      // sniffing for favicon stuff, IE workarounds and touchy-feely devices
+      ua = navigator.userAgent,
+      supportsFavicon = (ua.match(/(opera|firefox)/i)),
+      isTouchDevice = (ua.match(/ipad|ipod|iphone/i)),
+      cleanup;
+
+  // configuration options
+  // note that if Flash 9 is required, you must set soundManager.flashVersion = 9 in your script before this point.
+
+  this.config = {
+    usePeakData: false,     // [Flash 9 only]: show peak data
+    useWaveformData: false, // [Flash 9 only]: enable sound spectrum (raw waveform data) - WARNING: CPU-INTENSIVE: may set CPUs on fire.
+    useEQData: false,       // [Flash 9 only]: enable sound EQ (frequency spectrum data) - WARNING: Also CPU-intensive.
+    fillGraph: false,       // [Flash 9 only]: draw full lines instead of only top (peak) spectrum points
+    useMovieStar: true,     // [Flash 9 only]: Support for MPEG4 audio formats
+    allowRightClick: true,  // let users right-click MP3 links ("save as...", etc.) or discourage (can't prevent.)
+    useThrottling: true,    // try to rate-limit potentially-expensive calls (eg. dragging position around)
+    autoStart: false,       // begin playing first sound when page loads
+    playNext: true,         // stop after one sound, or play through list until end
+    updatePageTitle: true,  // change the page title while playing sounds
+    emptyTime: '-:--',      // null/undefined timer values (before data is available)
+    useFavIcon: false       // try to show peakData in address bar (Firefox + Opera) - may be too CPU heavy
+  };
+
+  this.css = {              // CSS class names appended to link during various states
+    sDefault: 'sm2_link',   // default state
+    sLoading: 'sm2_loading',
+    sPlaying: 'sm2_playing',
+    sPaused: 'sm2_paused'
+  };
+
+  this.sounds = [];
+  this.soundsByObject = [];
+  this.lastSound = null;
+  this.soundCount = 0;
+  this.strings = [];
+  this.dragActive = false;
+  this.dragExec = new Date();
+  this.dragTimer = null;
+  this.pageTitle = document.title;
+  this.lastWPExec = new Date();
+  this.lastWLExec = new Date();
+  this.vuMeterData = [];
+  this.oControls = null;
+
+  this._mergeObjects = function(oMain,oAdd) {
+    // non-destructive merge
+    var o1 = {}, o2, i, o; // clone o1
+    for (i in oMain) {
+      if (oMain.hasOwnProperty(i)) {
+        o1[i] = oMain[i];
+      }
+    }
+    o2 = (typeof oAdd === 'undefined'?{}:oAdd);
+    for (o in o2) {
+      if (typeof o1[o] === 'undefined') {
+        o1[o] = o2[o];
+      }
+    }
+    return o1;
+  };
+
+  _event = (function() {
+
+    var old = (window.attachEvent && !window.addEventListener),
+    _slice = Array.prototype.slice,
+    evt = {
+      add: (old?'attachEvent':'addEventListener'),
+      remove: (old?'detachEvent':'removeEventListener')
+    };
+
+    function getArgs(oArgs) {
+      var args = _slice.call(oArgs), len = args.length;
+      if (old) {
+        args[1] = 'on' + args[1]; // prefix
+        if (len > 3) {
+          args.pop(); // no capture
+        }
+      } else if (len === 3) {
+        args.push(false);
+      }
+      return args;
+    }
+
+    function apply(args, sType) {
+      var element = args.shift(),
+          method = [evt[sType]];
+      if (old) {
+        element[method](args[0], args[1]);
+      } else {
+        element[method].apply(element, args);
+      }
+    }
+
+    function add() {
+      apply(getArgs(arguments), 'add');
+    }
+
+    function remove() {
+      apply(getArgs(arguments), 'remove');
+    }
+
+    return {
+      'add': add,
+      'remove': remove
+    };
+
+  }());
+
+  this.hasClass = function(o, cStr) {
+    return (typeof(o.className)!=='undefined'?new RegExp('(^|\\s)'+cStr+'(\\s|$)').test(o.className):false);
+  };
+
+  this.addClass = function(o, cStr) {
+    if (!o || !cStr || self.hasClass(o,cStr)) {
+      return false; // safety net
+    }
+    o.className = (o.className?o.className+' ':'')+cStr;
+  };
+
+  this.removeClass = function(o, cStr) {
+    if (!o || !cStr || !self.hasClass(o,cStr)) {
+      return false;
+    }
+    o.className = o.className.replace(new RegExp('( '+cStr+')|('+cStr+')','g'),'');
+  };
+
+  this.select = function(className, oParent) {
+    var result = self.getByClassName(className, 'div', oParent||null);
+    return (result ? result[0] : null);
+  };
+
+  this.getByClassName = (document.querySelectorAll ? function(className, tagNames, oParent) { // tagNames: string or ['div', 'p'] etc.
+
+    var pattern = ('.'+className), qs;
+    if (tagNames) {
+      tagNames = tagNames.split(' ');
+    }
+    qs = (tagNames.length > 1 ? tagNames.join(pattern+', ') : tagNames[0]+pattern);
+    return (oParent?oParent:document).querySelectorAll(qs);
+
+  } : function(className, tagNames, oParent) {
+
+    var node = (oParent?oParent:document), matches = [], i, j, nodes = [];
+    if (tagNames) {
+      tagNames = tagNames.split(' ');
+    }
+    if (tagNames instanceof Array) {
+      for (i=tagNames.length; i--;) {
+        if (!nodes || !nodes[tagNames[i]]) {
+          nodes[tagNames[i]] = node.getElementsByTagName(tagNames[i]);
+        }
+      }
+      for (i=tagNames.length; i--;) {
+        for (j=nodes[tagNames[i]].length; j--;) {
+          if (self.hasClass(nodes[tagNames[i]][j], className)) {
+            matches.push(nodes[tagNames[i]][j]);
+          }
+        }
+      }
+    } else {
+      nodes = node.all||node.getElementsByTagName('*');
+      for (i=0, j=nodes.length; i<j; i++) {
+        if (self.hasClass(nodes[i],className)) {
+          matches.push(nodes[i]);
+        }
+      }
+    }
+    return matches;
+
+  });
+  
+  this.isChildOfClass = function(oChild, oClass) {
+    if (!oChild || !oClass) {
+      return false;
+    }
+    while (oChild.parentNode && !self.hasClass(oChild,oClass)) {
+      oChild = oChild.parentNode;
+    }
+    return (self.hasClass(oChild,oClass));
+  };
+
+  this.getParentByNodeName = function(oChild, sParentNodeName) {
+    if (!oChild || !sParentNodeName) {
+      return false;
+    }
+    sParentNodeName = sParentNodeName.toLowerCase();
+    while (oChild.parentNode && sParentNodeName !== oChild.parentNode.nodeName.toLowerCase()) {
+      oChild = oChild.parentNode;
+    }
+    return (oChild.parentNode && sParentNodeName === oChild.parentNode.nodeName.toLowerCase()?oChild.parentNode:null);
+  };
+
+  this.getOffX = function(o) {
+    // http://www.xs4all.nl/~ppk/js/findpos.html
+    var curleft = 0;
+    if (o.offsetParent) {
+      while (o.offsetParent) {
+        curleft += o.offsetLeft;
+        o = o.offsetParent;
+      }
+    }
+    else if (o.x) {
+      curleft += o.x;
+    }
+    return curleft;
+  };
+
+  this.getTime = function(nMSec, bAsString) {
+    // convert milliseconds to mm:ss, return as object literal or string
+    var nSec = Math.floor(nMSec/1000),
+        min = Math.floor(nSec/60),
+        sec = nSec-(min*60);
+    // if (min === 0 && sec === 0) return null; // return 0:00 as null
+    return (bAsString?(min+':'+(sec<10?'0'+sec:sec)):{'min':min,'sec':sec});
+  };
+
+  this.getSoundByObject = function(o) {
+    return (typeof self.soundsByObject[o.id] !== 'undefined'?self.soundsByObject[o.id]:null);
+  };
+
+  this.getPreviousItem = function(o) {
+    // given <li> playlist item, find previous <li> and then <a>
+    if (o.previousElementSibling) {
+      o = o.previousElementSibling;
+    } else {
+      o = o.previousSibling; // move from original node..
+      while (o && o.previousSibling && o.previousSibling.nodeType !== 1) {
+        o = o.previousSibling;
+      }
+    }
+    if (o.nodeName.toLowerCase() !== 'li') {
+      return null;
+    } else {
+      return o.getElementsByTagName('a')[0];
+    }
+  };
+
+  this.playPrevious = function(oSound) {
+    if (!oSound) {
+      oSound = self.lastSound;
+    }
+    if (!oSound) {
+      return false;
+    }
+    var previousItem = self.getPreviousItem(oSound._data.oLI);
+    if (previousItem) {
+      pl.handleClick({target:previousItem}); // fake a click event - aren't we sneaky. ;)
+    }
+    return previousItem;
+  };
+
+  this.getNextItem = function(o) {
+    // given <li> playlist item, find next <li> and then <a>
+    if (o.nextElementSibling) {
+      o = o.nextElementSibling;
+    } else {
+      o = o.nextSibling; // move from original node..
+      while (o && o.nextSibling && o.nextSibling.nodeType !== 1) {
+        o = o.nextSibling;
+      }
+    }
+    if (o.nodeName.toLowerCase() !== 'li') {
+      return null;
+    } else {
+      return o.getElementsByTagName('a')[0];
+    }
+  };
+
+  this.playNext = function(oSound) {
+    if (!oSound) {
+      oSound = self.lastSound;
+    }
+    if (!oSound) {
+      return false;
+    }
+    var nextItem = self.getNextItem(oSound._data.oLI);
+    if (nextItem) {
+      pl.handleClick({target:nextItem}); // fake a click event - aren't we sneaky. ;)
+    }
+    return nextItem;
+  };
+
+  this.setPageTitle = function(sTitle) {
+    if (!self.config.updatePageTitle) {
+      return false;
+    }
+    try {
+      document.title = (sTitle?sTitle+' - ':'')+self.pageTitle;
+    } catch(e) {
+      // oh well
+      self.setPageTitle = function() {
+        return false;
+      };
+    }
+  };
+
+  this.events = {
+
+    // handlers for sound events as they're started/stopped/played
+
+    play: function() {
+      pl.removeClass(this._data.oLI,this._data.className);
+      this._data.className = pl.css.sPlaying;
+      pl.addClass(this._data.oLI,this._data.className);
+      self.setPageTitle(this._data.originalTitle);
+    },
+
+    stop: function() {
+      pl.removeClass(this._data.oLI,this._data.className);
+      this._data.className = '';
+      this._data.oPosition.style.width = '0px';
+      self.setPageTitle();
+      self.resetPageIcon();
+    },
+
+    pause: function() {
+      if (pl.dragActive) {
+        return false;
+      }
+      pl.removeClass(this._data.oLI,this._data.className);
+      this._data.className = pl.css.sPaused;
+      pl.addClass(this._data.oLI,this._data.className);
+      self.setPageTitle();
+      self.resetPageIcon();
+    },
+
+    resume: function() {
+      if (pl.dragActive) {
+        return false;
+      }
+      pl.removeClass(this._data.oLI,this._data.className);
+      this._data.className = pl.css.sPlaying;
+      pl.addClass(this._data.oLI,this._data.className);
+    },
+
+    finish: function() {
+      pl.removeClass(this._data.oLI,this._data.className);
+      this._data.className = '';
+      this._data.oPosition.style.width = '0px';
+      // play next if applicable
+      if (self.config.playNext) {
+        pl.playNext(this);
+      } else {
+        self.setPageTitle();
+        self.resetPageIcon();
+      }
+    },
+
+    whileloading: function() {
+      function doWork() {
+        this._data.oLoading.style.width = (((this.bytesLoaded/this.bytesTotal)*100)+'%'); // theoretically, this should work.
+        if (!this._data.didRefresh && this._data.metadata) {
+          this._data.didRefresh = true;
+          this._data.metadata.refresh();
+        }
+      }
+      if (!pl.config.useThrottling) {
+        doWork.apply(this);
+      } else {
+        var d = new Date();
+        if (d && d-self.lastWLExec>30 || this.bytesLoaded === this.bytesTotal) {
+          doWork.apply(this);
+          self.lastWLExec = d;
+        }
+      }
+
+    },
+
+    onload: function() {
+      if (!this.loaded) {
+        var oTemp = this._data.oLI.getElementsByTagName('a')[0],
+            oString = oTemp.innerHTML,
+            oThis = this;
+        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>';
+        setTimeout(function(){
+          oTemp.innerHTML = oString;
+          // pl.events.finish.apply(oThis); // load next
+        },5000);
+      } else {
+        if (this._data.metadata) {
+          this._data.metadata.refresh();
+        }
+      }
+    },
+
+    whileplaying: function() {
+      var d = null;
+      if (pl.dragActive || !pl.config.useThrottling) {
+        self.updateTime.apply(this);
+        if (sm.flashVersion >= 9) {
+          if (pl.config.usePeakData && this.instanceOptions.usePeakData) {
+            self.updatePeaks.apply(this);
+          }
+          if (pl.config.useWaveformData && this.instanceOptions.useWaveformData || pl.config.useEQData && this.instanceOptions.useEQData) {
+            self.updateGraph.apply(this);
+          }
+        }
+        if (this._data.metadata) {
+          d = new Date();
+          if (d && d-self.lastWPExec>500) {
+            this._data.metadata.refreshMetadata(this);
+            self.lastWPExec = d;
+          }
+        }
+        this._data.oPosition.style.width = (((this.position/self.getDurationEstimate(this))*100)+'%');
+      } else {
+        d = new Date();
+        if (d-self.lastWPExec>30) {
+          self.updateTime.apply(this);
+          if (sm.flashVersion >= 9) {
+            if (pl.config.usePeakData && this.instanceOptions.usePeakData) {
+              self.updatePeaks.apply(this);
+            }
+            if (pl.config.useWaveformData && this.instanceOptions.useWaveformData || pl.config.useEQData && this.instanceOptions.useEQData) {
+              self.updateGraph.apply(this);
+            }
+          }
+          if (this._data.metadata) {
+            this._data.metadata.refreshMetadata(this);
+          }
+          this._data.oPosition.style.width = (((this.position/self.getDurationEstimate(this))*100)+'%');
+          self.lastWPExec = d;
+        }
+      }
+    }
+
+  }; // events{}
+
+  this.setPageIcon = function(sDataURL) {
+    if (!self.config.useFavIcon || !self.config.usePeakData || !sDataURL) {
+      return false;
+    }
+    var link = document.getElementById('sm2-favicon');
+    if (link) {
+      _head.removeChild(link);
+      link = null;
+    }
+    if (!link) {
+      link = document.createElement('link');
+      link.id = 'sm2-favicon';
+      link.rel = 'shortcut icon';
+      link.type = 'image/png';
+      link.href = sDataURL;
+      document.getElementsByTagName('head')[0].appendChild(link);
+    }
+  };
+
+  this.resetPageIcon = function() {
+    if (!self.config.useFavIcon) {
+      return false;
+    }
+    var link = document.getElementById('favicon');
+    if (link) {
+      link.href = '/favicon.ico';
+    }
+  };
+
+  this.updatePeaks = function() {
+    var o = this._data.oPeak,
+        oSpan = o.getElementsByTagName('span');
+    oSpan[0].style.marginTop = (13-(Math.floor(15*this.peakData.left))+'px');
+    oSpan[1].style.marginTop = (13-(Math.floor(15*this.peakData.right))+'px');
+    if (sm.flashVersion > 8 && self.config.useFavIcon && self.config.usePeakData) {
+      self.setPageIcon(self.vuMeterData[parseInt(16*this.peakData.left,10)][parseInt(16*this.peakData.right,10)]);
+    }
+  };
+  
+  this.updateGraph = function() {
+    if (pl.config.flashVersion < 9 || (!pl.config.useWaveformData && !pl.config.useEQData)) {
+      return false;
+    }
+    var sbC = this._data.oGraph.getElementsByTagName('div'),
+        scale, i, offset;
+    if (pl.config.useWaveformData) {
+      // raw waveform
+      scale = 8; // Y axis (+/- this distance from 0)
+      for (i=255; i--;) {
+        sbC[255-i].style.marginTop = (1+scale+Math.ceil(this.waveformData.left[i]*-scale))+'px';
+      }
+    } else {
+      // eq spectrum
+      offset = 9;
+      for (i=255; i--;) {
+        sbC[255-i].style.marginTop = ((offset*2)-1+Math.ceil(this.eqData[i]*-offset))+'px';
+      }
+    }
+  };
+  
+  this.resetGraph = function() {
+    if (!pl.config.useEQData || pl.config.flashVersion<9) {
+      return false;
+    }
+    var sbC = this._data.oGraph.getElementsByTagName('div'),
+        scale = (!pl.config.useEQData?'9px':'17px'),
+        nHeight = (!pl.config.fillGraph?'1px':'32px'),
+        i;
+    for (i=255; i--;) {
+      sbC[255-i].style.marginTop = scale; // EQ scale
+      sbC[255-i].style.height = nHeight;
+    }
+  };
+  
+  this.updateTime = function() {
+    var str = self.strings.timing.replace('%s1',self.getTime(this.position,true));
+    str = str.replace('%s2',self.getTime(self.getDurationEstimate(this),true));
+    this._data.oTiming.innerHTML = str;
+
+    var startMatchOffset = document.getElementById("mysongCurrentPos");
+    startMatchOffset.innerHTML = self.getTime(this.position,true) + " secs";
+    startMatchOffset.setAttribute("offsetInMSecs",this.position);
+  };
+
+  this.getTheDamnTarget = function(e) {
+    return (e.target||(window.event?window.event.srcElement:null));
+  };
+  
+  this.withinStatusBar = function(o) {
+    return (self.isChildOfClass(o,'controls'));
+  };
+
+  this.handleClick = function(e) {
+
+    // a sound (or something) was clicked - determine what and handle appropriately
+
+    if (e.button === 2) {
+      if (!pl.config.allowRightClick) {
+        pl.stopEvent(e);
+      }
+      return pl.config.allowRightClick; // ignore right-clicks
+    }
+    var o = self.getTheDamnTarget(e),
+        sURL, soundURL, thisSound, oControls, oLI, str;
+    if (!o) {
+      return true;
+    }
+    if (self.dragActive) {
+      self.stopDrag(); // to be safe
+    }
+    if (self.withinStatusBar(o)) {
+      // self.handleStatusClick(e);
+      return false;
+    }
+    if (o.nodeName.toLowerCase() !== 'a') {
+      o = self.getParentByNodeName(o,'a');
+    }
+    if (!o) {
+      // not a link
+      return true;
+    }
+
+    // OK, we're dealing with a link
+
+    sURL = o.getAttribute('href');
+
+    if (!o.href || (!sm.canPlayLink(o) && !self.hasClass(o,'playable')) || self.hasClass(o,'exclude')) {
+
+      // do nothing, don't return anything.
+      return true;
+
+    } else {
+
+      // we have something we're interested in.
+
+      // find and init parent UL, if need be
+      self.initUL(self.getParentByNodeName(o, 'ul'));
+
+      // and decorate the link too, if needed
+      self.initItem(o);
+
+      soundURL = o.href;
+      thisSound = self.getSoundByObject(o);
+
+      if (thisSound) {
+
+        // sound already exists
+        self.setPageTitle(thisSound._data.originalTitle);
+        if (thisSound === self.lastSound) {
+          // ..and was playing (or paused) and isn't in an error state
+          if (thisSound.readyState !== 2) {
+            if (thisSound.playState !== 1) {
+              // not yet playing
+              thisSound.play();
+            } else {
+              thisSound.togglePause();
+            }
+          } else {
+            sm._writeDebug('Warning: sound failed to load (security restrictions, 404 or bad format)',2);
+          }
+        } else {
+          // ..different sound
+          if (self.lastSound) {
+            self.stopSound(self.lastSound);
+          }
+          if (spectrumContainer) {
+            thisSound._data.oTimingBox.appendChild(spectrumContainer);
+          }
+          thisSound.togglePause(); // start playing current
+        }
+
+      } else {
+
+        // create sound
+        thisSound = sm.createSound({
+          id:o.id,
+          url:decodeURI(soundURL),
+          onplay:self.events.play,
+          onstop:self.events.stop,
+          onpause:self.events.pause,
+          onresume:self.events.resume,
+          onfinish:self.events.finish,
+          whileloading:self.events.whileloading,
+          whileplaying:self.events.whileplaying,
+          onmetadata:self.events.metadata,
+          onload:self.events.onload
+        });
+
+        // append control template
+        oControls = self.oControls.cloneNode(true);
+        oLI = o.parentNode;
+        oLI.appendChild(oControls);
+        if (spectrumContainer) {
+          oLI.appendChild(spectrumContainer);
+        }
+        self.soundsByObject[o.id] = thisSound;
+
+        // tack on some custom data
+        thisSound._data = {
+          oLink: o, // DOM reference within SM2 object event handlers
+          oLI: oLI,
+          oControls: self.select('controls',oLI),
+          oStatus: self.select('statusbar',oLI),
+          oLoading: self.select('loading',oLI),
+          oPosition: self.select('position',oLI),
+          oTimingBox: self.select('timing',oLI),
+          oTiming: self.select('timing',oLI).getElementsByTagName('div')[0],
+          oPeak: self.select('peak',oLI),
+          oGraph: self.select('spectrum-box',oLI),
+          className: self.css.sPlaying,
+          originalTitle: o.innerHTML,
+          metadata: null
+        };
+
+        if (spectrumContainer) {
+          thisSound._data.oTimingBox.appendChild(spectrumContainer);
+        }
+
+        // "Metadata"
+        if (thisSound._data.oLI.getElementsByTagName('ul').length) {
+          thisSound._data.metadata = new Metadata(thisSound);
+        }
+
+        // set initial timer stuff (before loading)
+        str = self.strings.timing.replace('%s1',self.config.emptyTime);
+        str = str.replace('%s2',self.config.emptyTime);
+        thisSound._data.oTiming.innerHTML = str;
+        self.sounds.push(thisSound);
+        if (self.lastSound) {
+          self.stopSound(self.lastSound);
+        }
+        self.resetGraph.apply(thisSound);
+        thisSound.play();
+
+      }
+
+      self.lastSound = thisSound; // reference for next call
+      return self.stopEvent(e);
+
+    }
+
+  };
+  
+  this.handleMouseDown = function(e) {
+    // a sound link was clicked
+    if (isTouchDevice && e.touches) {
+      e = e.touches[0];
+    }
+    if (e.button === 2) {
+      if (!pl.config.allowRightClick) {
+        pl.stopEvent(e);
+      }
+      return pl.config.allowRightClick; // ignore right-clicks
+    }
+    var o = self.getTheDamnTarget(e);
+    if (!o) {
+      return true;
+    }
+    if (!self.withinStatusBar(o)) {
+      return true;
+    }
+    self.dragActive = true;
+    self.lastSound.pause();
+    self.setPosition(e);
+    if (!isTouchDevice) {
+      _event.add(document,'mousemove',self.handleMouseMove);
+    } else {
+      _event.add(document,'touchmove',self.handleMouseMove);
+    }
+    self.addClass(self.lastSound._data.oControls,'dragging');
+    return self.stopEvent(e);
+  };
+  
+  this.handleMouseMove = function(e) {
+    if (isTouchDevice && e.touches) {
+      e = e.touches[0];
+    }
+    // set position accordingly
+    if (self.dragActive) {
+      if (self.config.useThrottling) {
+        // be nice to CPU/externalInterface
+        var d = new Date();
+        if (d-self.dragExec>20) {
+          self.setPosition(e);
+        } else {
+          window.clearTimeout(self.dragTimer);
+          self.dragTimer = window.setTimeout(function(){self.setPosition(e);},20);
+        }
+        self.dragExec = d;
+      } else {
+        // oh the hell with it
+        self.setPosition(e);
+      }
+    } else {
+      self.stopDrag();
+    }
+    e.stopPropagation = true;
+    return false;
+  };
+  
+  this.stopDrag = function(e) {
+    if (self.dragActive) {
+      self.removeClass(self.lastSound._data.oControls,'dragging');
+      if (!isTouchDevice) {
+        _event.remove(document,'mousemove',self.handleMouseMove);
+      } else {
+        _event.remove(document,'touchmove',self.handleMouseMove);
+      }
+      if (!pl.hasClass(self.lastSound._data.oLI,self.css.sPaused)) {
+        self.lastSound.resume();
+      }
+      self.dragActive = false;
+      return self.stopEvent(e);
+    }
+  };
+  
+  this.handleStatusClick = function(e) {
+    self.setPosition(e);
+    if (!pl.hasClass(self.lastSound._data.oLI,self.css.sPaused)) {
+      self.resume();
+    }
+    return self.stopEvent(e);
+  };
+  
+  this.stopEvent = function(e) {
+    if (typeof e !== 'undefined') {
+      if (typeof e.preventDefault !== 'undefined') {
+        e.preventDefault();
+      } else {
+        e.stopPropagation = true;
+        e.returnValue = false;
+      }
+    }
+    return false;
+  };
+ 
+  this.setPosition = function(e) {
+    // called from slider control
+    var oThis = self.getTheDamnTarget(e),
+        x, oControl, oSound, nMsecOffset;
+    if (!oThis) {
+      return true;
+    }
+    oControl = oThis;
+    while (!self.hasClass(oControl,'controls') && oControl.parentNode) {
+      oControl = oControl.parentNode;
+    }
+    oSound = self.lastSound;
+    x = parseInt(e.clientX,10);
+    // play sound at this position
+    nMsecOffset = Math.floor((x-self.getOffX(oControl)-4)/(oControl.offsetWidth)*self.getDurationEstimate(oSound));
+    if (!isNaN(nMsecOffset)) {
+      nMsecOffset = Math.min(nMsecOffset,oSound.duration);
+    }
+    if (!isNaN(nMsecOffset)) {
+      oSound.setPosition(nMsecOffset);
+    }
+  };
+
+  this.stopSound = function(oSound) {
+    sm._writeDebug('stopping sound: '+oSound.sID);
+    sm.stop(oSound.sID);
+    if (!isTouchDevice) { // iOS 4.2+ security blocks onfinish() -> playNext() if we set a .src in-between(?)
+      sm.unload(oSound.sID);
+    }
+  };
+
+  this.getDurationEstimate = function(oSound) {
+    if (oSound.instanceOptions.isMovieStar) {
+      return (oSound.duration);
+    } else {
+      return (!oSound._data.metadata || !oSound._data.metadata.data.givenDuration ? (oSound.durationEstimate||0) : oSound._data.metadata.data.givenDuration);
+    }
+  };
+
+  this.createVUData = function() {
+
+    var i=0, j=0,
+      canvas = vuDataCanvas.getContext('2d'),
+      vuGrad = canvas.createLinearGradient(0, 16, 0, 0),
+      bgGrad, outline;
+
+    vuGrad.addColorStop(0,'rgb(0,192,0)');
+    vuGrad.addColorStop(0.30,'rgb(0,255,0)');
+    vuGrad.addColorStop(0.625,'rgb(255,255,0)');
+    vuGrad.addColorStop(0.85,'rgb(255,0,0)');
+    bgGrad = canvas.createLinearGradient(0, 16, 0, 0);
+    outline = 'rgba(0,0,0,0.2)';
+    bgGrad.addColorStop(0,outline);
+    bgGrad.addColorStop(1,'rgba(0,0,0,0.5)');
+    for (i=0; i<16; i++) {
+      self.vuMeterData[i] = [];
+    }
+    for (i=0; i<16; i++) {
+      for (j=0; j<16; j++) {
+        // reset/erase canvas
+        vuDataCanvas.setAttribute('width',16);
+        vuDataCanvas.setAttribute('height',16);
+        // draw new stuffs
+        canvas.fillStyle = bgGrad;
+        canvas.fillRect(0,0,7,15);
+        canvas.fillRect(8,0,7,15);
+        /*
+        // shadow
+        canvas.fillStyle = 'rgba(0,0,0,0.1)';
+        canvas.fillRect(1,15-i,7,17-(17-i));
+        canvas.fillRect(9,15-j,7,17-(17-j));
+        */
+        canvas.fillStyle = vuGrad;
+        canvas.fillRect(0,15-i,7,16-(16-i));
+        canvas.fillRect(8,15-j,7,16-(16-j));
+        // and now, clear out some bits.
+        canvas.clearRect(0,3,16,1);
+        canvas.clearRect(0,7,16,1);
+        canvas.clearRect(0,11,16,1);
+        self.vuMeterData[i][j] = vuDataCanvas.toDataURL('image/png');
+        // for debugging VU images
+        /*
+        var o = document.createElement('img');
+        o.style.marginRight = '5px'; 
+        o.src = self.vuMeterData[i][j];
+        document.documentElement.appendChild(o);
+        */
+      }
+    }
+
+  };
+
+  this.testCanvas = function() {
+    // canvas + toDataURL();
+    var c = document.createElement('canvas'),
+        ctx = null, ok;
+    if (!c || typeof c.getContext === 'undefined') {
+      return null;
+    }
+    ctx = c.getContext('2d');
+    if (!ctx || typeof c.toDataURL !== 'function') {
+        return null;
+    }
+    // just in case..
+    try {
+        ok = c.toDataURL('image/png');
+    } catch(e) {
+      // no canvas or no toDataURL()
+      return null;
+    }
+    // assume we're all good.
+    return c;
+  };
+
+  this.initItem = function(oNode) {
+    if (!oNode.id) {
+      oNode.id = 'pagePlayerMP3Sound'+(self.soundCount++);
+    }
+    self.addClass(oNode,self.css.sDefault); // add default CSS decoration
+  };
+
+  this.initUL = function(oULNode) {
+    // set up graph box stuffs
+    if (sm.flashVersion >= 9) {
+        self.addClass(oULNode,self.cssBase);
+    }
+  };
+
+  this.init = function(oConfig) {
+
+    if (oConfig) {
+      // allow overriding via arguments object
+      sm._writeDebug('pagePlayer.init(): Using custom configuration');
+      this.config = this._mergeObjects(oConfig,this.config);
+    } else {
+      sm._writeDebug('pagePlayer.init(): Using default configuration');
+    }
+
+    var i, spectrumBox, sbC, oF, oClone, oTiming;
+
+    // apply externally-defined override, if applicable
+    this.cssBase = []; // optional features added to ul.playlist
+
+    // apply some items to SM2
+    sm.useFlashBlock = true;
+
+    if (sm.flashVersion >= 9) {
+
+      sm.useMovieStar = this.config.useMovieStar; // enable playing FLV, MP4 etc.
+      sm.defaultOptions.usePeakData = this.config.usePeakData;
+      sm.defaultOptions.useWaveformData = this.config.useWaveformData;
+      sm.defaultOptions.useEQData = this.config.useEQData;
+
+      if (this.config.usePeakData) {
+        this.cssBase.push('use-peak');
+      }
+
+      if (this.config.useWaveformData || this.config.useEQData) {
+        this.cssBase.push('use-spectrum');
+      }
+
+      this.cssBase = this.cssBase.join(' ');
+
+      if (this.config.useFavIcon) {
+        vuDataCanvas = self.testCanvas();
+        if (vuDataCanvas && supportsFavicon) {
+          // these browsers support dynamically-updating the favicon
+          self.createVUData();
+        } else {
+          // browser doesn't support doing this
+          this.config.useFavIcon = false;
+        }
+      }
+
+    } else if (this.config.usePeakData || this.config.useWaveformData || this.config.useEQData) {
+
+      sm._writeDebug('Page player: Note: soundManager.flashVersion = 9 is required for peak/waveform/EQ features.');
+
+    }
+
+    controlTemplate = document.createElement('div');
+
+     controlTemplate.innerHTML = [
+
+      // control markup inserted dynamically after each page player link
+      // if you want to change the UI layout, this is the place to do it.
+
+      '  <div class="controls">',
+      '   <div class="statusbar">',
+      '    <div class="loading"></div>',
+      '    <div class="position"></div>',
+      '   </div>',
+      '  </div>',
+
+      '  <div class="timing">',
+      '   <div id="sm2_timing" class="timing-data">',
+      '    <span class="sm2_position">%s1</span> / <span class="sm2_total">%s2</span>',
+      '   </div>',
+      '  </div>',
+
+      '  <div class="peak">',
+      '   <div class="peak-box"><span class="l"></span><span class="r"></span></div>',
+      '  </div>',
+
+      ' <div class="spectrum-container">',
+      '  <div class="spectrum-box">',
+      '   <div class="spectrum"></div>',
+      '  </div>',
+      ' </div>'
+
+    ].join('\n');
+
+    if (sm.flashVersion >= 9) {
+
+      // create the spectrum box ish
+      spectrumContainer = self.select('spectrum-container',controlTemplate);
+
+      // take out of template, too
+      spectrumContainer = controlTemplate.removeChild(spectrumContainer);
+
+      spectrumBox = self.select('spectrum-box',spectrumContainer);
+
+      sbC = spectrumBox.getElementsByTagName('div')[0];
+      oF = document.createDocumentFragment();
+      oClone = null;
+      for (i=256; i--;) {
+        oClone = sbC.cloneNode(false);
+        oClone.style.left = (i)+'px';
+        oF.appendChild(oClone);
+      }
+      spectrumBox.removeChild(sbC);
+      spectrumBox.appendChild(oF);
+
+    } else {
+
+      // flash 8-only, take out the spectrum container and peak elements
+      controlTemplate.removeChild(self.select('spectrum-container',controlTemplate));
+      controlTemplate.removeChild(self.select('peak',controlTemplate));
+
+    }
+
+    self.oControls = controlTemplate.cloneNode(true);
+
+    oTiming = self.select('timing-data',controlTemplate);
+    self.strings.timing = oTiming.innerHTML;
+    oTiming.innerHTML = '';
+    oTiming.id = '';
+
+    function doEvents(action) { // action: add / remove
+
+      _event[action](document,'click',self.handleClick);
+
+      if (!isTouchDevice) {
+        _event[action](document,'mousedown',self.handleMouseDown);
+        _event[action](document,'mouseup',self.stopDrag);
+      } else {
+        _event[action](document,'touchstart',self.handleMouseDown);
+        _event[action](document,'touchend',self.stopDrag);
+      }
+
+      _event[action](window, 'unload', cleanup);
+
+    }
+
+    cleanup = function() {
+      doEvents('remove');
+    };
+
+    doEvents('add');
+
+    sm._writeDebug('pagePlayer.init(): Ready',1);
+
+    if (self.config.autoStart) {
+      // grab the first ul.playlist link
+      pl.handleClick({target:pl.getByClassName('playlist', 'ul')[0].getElementsByTagName('a')[0]});
+    }
+
+  };
+
+}
+
+soundManager.useFlashBlock = true;
+
+soundManager.onready(function() {
+  pagePlayer = new PagePlayer();
+  pagePlayer.init(typeof PP_CONFIG !== 'undefined' ? PP_CONFIG : null);
+});
