/*jslint plusplus: true, white: true, nomen: true */
/* global soundManager, document, console, window */

(function(window) {

  /**
   * SoundManager 2: "Bar UI" player
   * http://www.schillmania.com/projects/soundmanager2/
   */

  "use strict";

  var Player,
      players = [],
      // CSS selector that will get us the top-level DOM node for the player UI.
      playerSelector = '.sm2-bar-ui',
      utils;

  soundManager.setup({
    // trade-off: higher UI responsiveness (play/progress bar), but may use more CPU.
    html5PollingInterval: 50,
    flashVersion: 9
  });

  soundManager.onready(function() {

    var nodes,
      i, j;

    nodes = utils.dom.getAll(playerSelector);

    if (nodes && nodes.length) {
      for (i=0, j=nodes.length; i<j; i++) {
        players.push(new Player(nodes[i]));
      }
    }
  
  });

  utils = {

    array: (function() {

      function compare(property) {

        var result;

        return function(a, b) {

          if (a[property] < b[property]) {
            result = -1;
          } else if (a[property] > b[property]) {
            result = 1;
          } else {
            result = 0;
          }
          return result;
        };

      }

      function shuffle(array) {

        // Fisher-Yates shuffle algo

        var i, j, temp;

        for (i = array.length - 1; i > 0; i--) {
          j = Math.floor(Math.random() * (i+1));
          temp = array[i];
          array[i] = array[j];
          array[j] = temp;
        }

        return array;

      }

      return {
        compare: compare,
        shuffle: shuffle
      };

    }()),

    css: (function() {

      function hasClass(o, cStr) {

        return (o.className !== undefined ? new RegExp('(^|\\s)' + cStr + '(\\s|$)').test(o.className) : false);

      }

      function addClass(o, cStr) {

        if (!o || !cStr || hasClass(o, cStr)) {
          return false; // safety net
        }
        o.className = (o.className ? o.className + ' ' : '') + cStr;

      }

      function removeClass(o, cStr) {

        if (!o || !cStr || !hasClass(o, cStr)) {
          return false;
        }
        o.className = o.className.replace(new RegExp('( ' + cStr + ')|(' + cStr + ')', 'g'), '');

      }

      function swapClass(o, cStr1, cStr2) {

        var tmpClass = {
          className: o.className
        };

        removeClass(tmpClass, cStr1);
        addClass(tmpClass, cStr2);

        o.className = tmpClass.className;

      }

      function toggleClass(o, cStr) {

        var found,
            method;

        found = hasClass(o, cStr);

        method = (found ? removeClass : addClass);

        method(o, cStr);

        // indicate the new state...
        return !found;

      }

      return {
        has: hasClass,
        add: addClass,
        remove: removeClass,
        swap: swapClass,
        toggle: toggleClass
      };

    }()),

    dom: (function() {

      function getAll(/* parentNode, selector */) {

        var node,
            selector,
            results;

        if (arguments.length === 1) {

          // .selector case
          node = document.documentElement;
          selector = arguments[0];

        } else {

          // node, .selector
          node = arguments[0];
          selector = arguments[1];

        }

        // sorry, IE 7 users; IE 8+ required.
        if (node && node.querySelectorAll) {

          results = node.querySelectorAll(selector);

        }

        return results;

      }

      function get(/* parentNode, selector */) {

        var results = getAll.apply(this, arguments);

        // hackish: if more than one match and no third argument, return the last.
        if (results && results.length) {
          results = results[results.length-1];
        }

        return results;

      }

      return {
        get: get,
        getAll: getAll
      };

    }()),

    position: (function() {

      function getOffX(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;

      }

      function getOffY(o) {

        // http://www.xs4all.nl/~ppk/js/findpos.html
        var curtop = 0;

        if (o.offsetParent) {

          while (o.offsetParent) {

            curtop += o.offsetTop;

            o = o.offsetParent;

          }

        } else if (o.y) {

            curtop += o.y;

        }

        return curtop;

      }

      return {
        getOffX: getOffX,
        getOffY: getOffY
      };

    }()),

    style: (function() {

      function get(node, styleProp) {

        // http://www.quirksmode.org/dom/getstyles.html
        var value;

        if (node.currentStyle) {

          value = node.currentStyle[styleProp];

        } else if (window.getComputedStyle) {

          value = document.defaultView.getComputedStyle(node, null).getPropertyValue(styleProp);

        }

        return value;

      }

      return {
        get: get
      };

    }()),

    events: (function() {

      var add, remove, preventDefault;

      add = function(o, evtName, evtHandler) {
        // return an object with a convenient detach method.
        var eventObject = {
          detach: function() {
            return remove(o, evtName, evtHandler);
          }
        };
        if (window.addEventListener) {
          o.addEventListener(evtName, evtHandler, false);
        } else {
          o.attachEvent('on' + evtName, evtHandler);
        }
        return eventObject;
      };

      remove = (window.removeEventListener !== undefined ? function(o, evtName, evtHandler) {
        return o.removeEventListener(evtName, evtHandler, false);
      } : function(o, evtName, evtHandler) {
        return o.detachEvent('on' + evtName, evtHandler);
      });

      preventDefault = function(e) {
        if (e.preventDefault) {
          e.preventDefault();
        } else {
          e.returnValue = false;
          e.cancelBubble = true;
        }
        return false;
      };

      return {
        add: add,
        preventDefault: preventDefault,
        remove: remove
      };

    }()),

    features: (function() {

    	var getAnimationFrame,
    	    localAnimationFrame,
            localFeatures,
            prop,
            styles,
    	    testDiv,
    	    transform;

        testDiv = document.createElement('div');

	    /**
		 * hat tip: paul irish
		 * http://paulirish.com/2011/requestanimationframe-for-smart-animating/
		 * https://gist.github.com/838785
		 */

	    localAnimationFrame = (window.requestAnimationFrame
	      || window.webkitRequestAnimationFrame
	      || window.mozRequestAnimationFrame
	      || window.oRequestAnimationFrame
	      || window.msRequestAnimationFrame
	      || null);

	    // apply to window, avoid "illegal invocation" errors in Chrome
	    getAnimationFrame = localAnimationFrame ? function() {
	      return localAnimationFrame.apply(window, arguments);
	    } : null;

	    function has(prop) {

	      // test for feature support
	      var result = testDiv.style[prop];

	      return (result !== undefined ? prop : null);

	    }

	    // note local scope.
	    localFeatures = {

	      transform: {
	        ie: has('-ms-transform'),
	        moz: has('MozTransform'),
	        opera: has('OTransform'),
	        webkit: has('webkitTransform'),
	        w3: has('transform'),
	        prop: null // the normalized property value
	      },

	      rotate: {
	        has3D: false,
	        prop: null
	      },

	      getAnimationFrame: getAnimationFrame

	    };

	    localFeatures.transform.prop = (
	      localFeatures.transform.w3 ||
	      localFeatures.transform.moz ||
	      localFeatures.transform.webkit ||
	      localFeatures.transform.ie ||
	      localFeatures.transform.opera
	    );

	    function attempt(style) {

	      try {
	        testDiv.style[transform] = style;
	      } catch(e) {
	        // that *definitely* didn't work.
	        return false;
	      }
	      // if we can read back the style, it should be cool.
	      return !!testDiv.style[transform];

	    }

	    if (localFeatures.transform.prop) {

	      // try to derive the rotate/3D support.
	      transform = localFeatures.transform.prop;
	      styles = {
	        css_2d: 'rotate(0deg)',
	        css_3d: 'rotate3d(0,0,0,0deg)'
	      };

	      if (attempt(styles.css_3d)) {
	        localFeatures.rotate.has3D = true;
	        prop = 'rotate3d';
	      } else if (attempt(styles.css_2d)) {
	        prop = 'rotate';
	      }

	      localFeatures.rotate.prop = prop;

	    }

	    testDiv = null;

	    return localFeatures;

    }())

  };

  /**
   * player bits
   */

  Player = function(playerNode) {

      var css, dom, extras, playlistController, soundObject, actions, actionData, defaultItem;

	  css = {
	    disabled: 'disabled',
	    selected: 'selected',
	    active: 'active',
	    legacy: 'legacy',
	    noVolume: 'no-volume'
	  };

	  dom = {
	    o: null,
	    playlist: null,
        playlistTarget: null,
        playlistContainer: null,
	    time: null,
	    player: null,
	    progress: null,
	    progressTrack: null,
	    progressBar: null,
	    duration: null,
	    volume: null
	  };

      // prepended to tracks when a sound fails to load/play
      extras = {
      	loadFailedCharacter: '<span title="Failed to load/play." class="load-error">✖</span>'
      };

	  function PlaylistController() {

	    var data;

	    data = {

	      // list of nodes?
	      playlist: [],

	      // shuffledIndex: [],

	      // selection
	      selectedIndex: 0,

	      // shuffleMode: false,

	      loopMode: false,

          timer: null

	    };

	    function getPlaylist() {

	      return data.playlist;

	    }

	    function getItem(offset) {

	      var list,
	          item;

	      // given the current selection (or an offset), return the current item.

	      // if currently null, may be end of list case. bail.
	      if (data.selectedIndex === null) {
	        return offset;
	      }

	      list = getPlaylist();

	      // use offset if provided, otherwise take default selected.
	      offset = (offset !== undefined ? offset : data.selectedIndex);

	      // safety check - limit to between 0 and list length
	      offset = Math.max(0, Math.min(offset, list.length));

	      item = list[offset];

	      return item;

	    }

	    function findOffsetFromItem(item) {

	      // given an <li> item, find it in the playlist array and return the index.
	      var list,
	          i,
	          j,
	          offset;

	      offset = -1;

	      list = getPlaylist();

	      if (list) {

	        for (i=0, j=list.length; i<j; i++) {
	          if (list[i] === item) {
	            offset = i;
	            break;
	          }
	        }

	      }

	      return offset;

	    }

	    function getNext() {

          // don't increment if null.
          if (data.selectedIndex !== null) {
	        data.selectedIndex++;
	      }

          if (data.playlist.length > 1) {

 	        if (data.selectedIndex >= data.playlist.length) {

	          if (data.loopMode) {

                // loop to beginning
	            data.selectedIndex = 0;

	          } else {

                // no change
                data.selectedIndex--;

                // end playback
                // data.selectedIndex = null;

	          }

	        }
	
          } else {

            data.selectedIndex = null;

          }

	      return getItem();

	    }

	    function getPrevious() {

	      data.selectedIndex--;

	      if (data.selectedIndex < 0) {
	        // wrapping around beginning of list? loop or exit.
	        if (data.loopMode) {
	          data.selectedIndex = data.playlist.length - 1;
	        } else {
	          // undo
	          data.selectedIndex++;
	        }
	      }

	      return getItem();

	    }

	    function resetLastSelected() {

	      // remove UI highlight(s) on selected items.
	      var items,
	          i, j;

	      items = utils.dom.getAll(dom.playlist, '.' + css.selected);

	      for (i=0, j=items.length; i<j; i++) {
	        utils.css.remove(items[i], css.selected);
	      }

	    }

	    function select(item) {

	      var offset;

	      // remove last selected, if any
	      resetLastSelected();

	      if (item) {
	        utils.css.add(item, css.selected);
	      }

	      // update selected offset, too.
	      offset = findOffsetFromItem(item);

	      data.selectedIndex = offset;

	    }

/*
	    function selectOffset(offset) {

	      var item;

	      item = getItem(offset);

	      if (item) {
	        select(item);
	      }

	    }

	    function playItem(item) {

	      // given an item (<li> or <a>), find it in the playlist array, select and play it.

	      var list, offset;

	      list = getPlaylist();

	      if (list) {

	        offset = findOffsetFromItem(item);

	        if (offset !== -1) {

	          select(offset);

	        }

	      }

	    }

*/

	    function getURL() {

	      // return URL of currently-selected item
	      var item, url;

	      item = getItem();

      
	      if (item) {
	        url = item.getElementsByTagName('a')[0].href;
	      }

	      return url;

	    }

	    function refreshDOM() {

	      // get / update playlist from DOM

	      if (!dom.playlist) {
	      	if (window.console && console.warn) {
	        	console.warn('refreshDOM(): playlist node not found?');
	        }
	        return false;
	      }

	      data.playlist = dom.playlist.getElementsByTagName('li');

	    }

	    function initDOM() {

		  dom.playlistTarget = utils.dom.get(dom.o, '.sm2-playlist-target');
	      dom.playlistContainer = utils.dom.get(dom.o, '.sm2-playlist-drawer');
	      dom.playlist = utils.dom.get(dom.o, '.sm2-playlist-bd');

	    }

	    function init() {

	      initDOM();

	      refreshDOM();

	    }

	    init();

	    return {
	      data: data,
	      refresh: refreshDOM,
	      getNext: getNext,
	      getPrevious: getPrevious,
	      getItem: getItem,
	      getURL: getURL,
	      select: select
	    };

	  }

	  function getTime(msec, useString) {

	    // convert milliseconds to hh:mm:ss, return as object literal or string

	    var nSec = Math.floor(msec/1000),
	        hh = Math.floor(nSec/3600),
	        min = Math.floor(nSec/60) - Math.floor(hh * 60),
	        sec = Math.floor(nSec -(hh*3600) -(min*60));

	    // if (min === 0 && sec === 0) return null; // return 0:00 as null

	    return (useString ? ((hh ? hh + ':' : '') + (hh && min < 10 ? '0' + min : min) + ':' + ( sec < 10 ? '0' + sec : sec ) ) : { 'min': min, 'sec': sec });

	  }

	  function setTitle(item) {

	    // given a link, update the "now playing" UI.

	    // if this is an <li> with an inner link, grab and use the text from that.
	    var links = item.getElementsByTagName('a');

	    if (links.length) {
	      item = links[0];
	    }

        // remove any failed character sequence, also
	    //dom.playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li>' + item.innerHTML.replace(extras.loadFailedCharacter, '') + '</li></ul>';

	    //if (dom.playlistTarget.getElementsByTagName('li')[0].scrollWidth > dom.playlistTarget.offsetWidth) {
	      // this item can use <marquee>, in fact.
	      dom.playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li><marquee>' + item.innerHTML + '</marquee></li></ul>';
	    //}

	  }

	  function makeSound(url) {

	    var sound = soundManager.createSound({
	      id: 'sm-player',
	      url: url,

	      whileplaying: function() {
	        var progressMaxLeft = 100,
	            left,
	            width;
	
	        left = Math.min(progressMaxLeft, Math.max(0, (progressMaxLeft * (this.position / this.durationEstimate)))) + '%';
	        width = Math.min(100, Math.max(0, (100 * this.position / this.durationEstimate))) + '%';
	
	        if (this.duration) {

  	          dom.progress.style.left = left;
	          dom.progressBar.style.width = width;
              
	          // TODO: only write changes
	          dom.time.innerHTML = getTime(this.position, true);

	        }

	      },

	      onbufferchange: function(isBuffering) {
	        if (isBuffering) {
	          utils.css.add(dom.o, 'buffering');
	        } else {
	          utils.css.remove(dom.o, 'buffering');
	        }
	      },

	      onplay: function() {
	        utils.css.swap(dom.o, 'paused', 'playing');
	      },

	      onpause: function() {
	        utils.css.swap(dom.o, 'playing', 'paused');
	      },

	      onresume: function() {
	        utils.css.swap(dom.o, 'paused', 'playing');
	      },

	      whileloading: function() {

	        if (!this.isHTML5) {
	          dom.duration.innerHTML = getTime(this.durationEstimate, true);
	        }

	      },

	      onload: function(ok) {

	        if (ok) {

	          dom.duration.innerHTML = getTime(this.duration, true);

	        } else if (this._iO && this._iO.onerror) {

	          this._iO.onerror();

	        }

	      },

	      onerror: function() {

	        // sound failed to load.
	        var item, element, html;

	        item = playlistController.getItem();

	        if (item) {

	          // note error, delay 2 seconds and advance?
	          // playlistTarget.innerHTML = '<ul class="sm2-playlist-bd"><li>' + item.innerHTML + '</li></ul>';

	          if (extras.loadFailedCharacter) {
	            dom.playlistTarget.innerHTML = dom.playlistTarget.innerHTML.replace('<li>' ,'<li>' + extras.loadFailedCharacter + ' ');
	            if (playlistController.data.playlist && playlistController.data.playlist[playlistController.data.selectedIndex]) {
	              element = playlistController.data.playlist[playlistController.data.selectedIndex].getElementsByTagName('a')[0];
	              html = element.innerHTML;
	              if (html.indexOf(extras.loadFailedCharacter) === -1) {
                  	element.innerHTML = extras.loadFailedCharacter + ' ' + html;
                  }
	            }
	          }

	        }

	        // load next, possibly with delay.
            
            if (navigator.userAgent.match(/mobile/i)) {
              // mobile will likely block the next play() call if there is a setTimeout() - so don't use one here.
	          actions.next();
	        } else {
	          if (playlistController.data.timer) {
	          	window.clearTimeout(playlistController.data.timer);
	          }
	          playlistController.data.timer = window.setTimeout(actions.next, 1000);
	        }

	      },

          onstop: function() {

            utils.css.remove(dom.o, 'playing');

          },

	      onfinish: function() {

	        var lastIndex, item;

	        utils.css.remove(dom.o, 'playing');

	        dom.progress.style.left = '0%';

	        lastIndex = playlistController.data.selectedIndex;

	        // next track?
	        item = playlistController.getNext();

            // don't play the same item over and over again, if at end of playlist etc.
	        if (item && playlistController.data.selectedIndex !== lastIndex) {

	          playlistController.select(item);

	          setTitle(item);

	          // play next
	          this.play({
	            url: playlistController.getURL()
	          });

	        }/* else {

	          // explicitly stop?
	          // this.stop();

	        }*/

	      }

	    });

	    return sound;

	  }

      function isRightClick(e) {
        // only pay attention to left clicks. old IE differs where there's no e.which, but e.button is 1 on left click.
        if (e && ((e.which && e.which === 2) || (e.which === undefined && e.button !== 1))) {
          // http://www.quirksmode.org/js/events_properties.html#button
          return true;
        }
      }

	  function handleMouseDown(e) {

	    var links,
	        target;

	    target = e.target || e.srcElement;

        if (isRightClick(e)) {
          return true;
        }

	    // normalize to <a>, if applicable.
	    if (target.nodeName.toLowerCase() !== 'a') {
	      links = target.getElementsByTagName('a');
	      if (links && links.length) {
	        target = target.getElementsByTagName('a')[0];
	      }
	    }

	    if (utils.css.has(target, 'sm2-volume-control')) {

	      // drag case for volume

	      actionData.volume.x = utils.position.getOffX(target);
	      actionData.volume.y = utils.position.getOffY(target);

	      actionData.volume.width = target.offsetWidth;
	      actionData.volume.height = target.offsetHeight;

	      // potentially dangerous: this should, but may not be a percentage-based value.
	      actionData.volume.backgroundSize = parseInt(utils.style.get(target, 'background-size'), 10);

	      // IE gives pixels even if background-size specified as % in CSS. Boourns.
	      if (window.navigator.userAgent.match(/msie|trident/i)) {
	        actionData.volume.backgroundSize = (actionData.volume.backgroundSize / actionData.volume.width) * 100;
	      }

	      utils.events.add(document, 'mousemove', actions.adjustVolume);
	      utils.events.add(document, 'mouseup', actions.releaseVolume);

	      // and apply right away
	      return actions.adjustVolume(e);

	    }

	  }

	  function playLink(link) {

	    // if a link is OK, play it.

	    if (soundManager.canPlayURL(link.href)) {

	      if (!soundObject) {
	        soundObject = makeSound(link.href);
	      }

	      // required to reset pause/play state on iOS so whileplaying() works? odd.
	      soundObject.stop();

	      playlistController.select(link.parentNode);

	      // TODO: ancestor('li')
	      setTitle(link.parentNode);

	      soundObject.play({
	        url: link.href,
	        position: 0
	      });

	    }

	  }

	  function handleClick(e) {

	    var evt,
	        target,
	        offset,
            targetNodeName,
	        methodName,
	        href,
	        handled;

	    evt = (e || window.event);

	    target = evt.target || evt.srcElement;


	    if (target && target.nodeName) {

          targetNodeName = target.nodeName.toLowerCase();

          if (targetNodeName !== 'a') {

            // old IE (IE 8) might return nested elements inside the <a>, eg., <b> etc. Try to find the parent <a>.

           if (target.parentNode) {

              do {
                target = target.parentNode;
                targetNodeName = target.nodeName.toLowerCase();
              } while (targetNodeName !== 'a' && target.parentNode);

              if (!target) {
                // something went wrong. bail.
                return false;
              }

            }

          }

	      if (targetNodeName === 'a') {

	        // yep, it's a link.

	        href = target.href;

	        if (soundManager.canPlayURL(href)) {
              // not excluded
              if (!utils.css.has(target, 'sm2-exclude')) {

	            // find this in the playlist

	            playLink(target);

                handled = true;

              }

	        } else {

	          // is this one of the action buttons, eg., play/pause, volume, etc.?
	          offset = target.href.lastIndexOf('#');

	          if (offset !== -1) {
	            methodName = target.href.substr(offset+1);
	            if (methodName && actions[methodName]) {
                  handled = true;
	              actions[methodName](e);
	            }
	          }

	        }

            // fall-through case

            if (handled) {
	          // prevent browser fall-through
	          return utils.events.preventDefault(evt);
            }

	      }

	    }

	  }

	  function handleMouse(e) {

        var target, barX, barWidth, x, newPosition, sound;

	    target = dom.progressTrack;

	    barX = utils.position.getOffX(target);
	    barWidth = target.offsetWidth;

	    x = (e.clientX - barX);

	    newPosition = (x / barWidth);

	    sound = soundObject;

	    if (sound && sound.duration) {

	      sound.setPosition(sound.duration * newPosition);

	      // a little hackish: ensure UI updates immediately with current position, even if audio is buffering and hasn't moved there yet.
	      sound._iO.whileplaying.apply(sound);

	    }

	    if (e.preventDefault) {
	      e.preventDefault();
	    }

	    return false;

	  }

	  function releaseMouse(e) {

	    utils.events.remove(document, 'mousemove', handleMouse);

	    utils.css.remove(dom.o, 'grabbing');

	    if (e.preventDefault) {
	      e.preventDefault();
	    }

        utils.events.remove(document, 'mouseup', releaseMouse);

	    return false;

	  }

	  function init() {

	    // init DOM?

	    if (!playerNode) {
	      console.warn('init(): No playerNode element?');
	    }

	    dom.o = playerNode;

	    // are we dealing with a crap browser? apply legacy CSS if so.
	    if (window.navigator.userAgent.match(/msie [678]/i)) {
	      utils.css.add(dom.o, css.legacy);
	    }

	    if (window.navigator.userAgent.match(/mobile/i)) {
	      // majority of mobile devices don't let HTML5 audio set volume.
	      utils.css.add(dom.o, css.noVolume);
	    }

	    dom.progress = utils.dom.get(dom.o, '.sm2-progress-ball');

	    dom.progressTrack = utils.dom.get(dom.o, '.sm2-progress-track');

	    dom.progressBar = utils.dom.get(dom.o, '.sm2-progress-bar');

	    dom.volume = utils.dom.get(dom.o, 'a.sm2-volume-control');

	    dom.duration = utils.dom.get(dom.o, '.sm2-inline-duration');

	    dom.time = utils.dom.get(dom.o, '.sm2-inline-time');

	    playlistController = new PlaylistController();

	    defaultItem = playlistController.getItem(0);

	    playlistController.select(defaultItem);

	    setTitle(defaultItem);

	    utils.events.add(dom.o, 'mousedown', handleMouseDown);

	    utils.events.add(dom.o, 'click', handleClick);

	    utils.events.add(dom.progressTrack, 'mousedown', function(e) {

          if (isRightClick(e)) {
            return true;
          }

	      utils.css.add(dom.o, 'grabbing');
	      utils.events.add(document, 'mousemove', handleMouse);
	      utils.events.add(document, 'mouseup', releaseMouse);

	      return handleMouse(e);

	    });

	  }

	  // ---

	  actionData = {

	    volume: {
	      x: 0,
	      y: 0,
	      width: 0,
	      height: 0,
	      backgroundSize: 0
	    }

	  };

	  actions = {

	    play: function(e) {

	      var target,
	          href;

	      target = e.target || e.srcElement;

	      href = target.href;

	      // haaaack - if '#' due to play/pause link, get first link from playlist
	      if (href.indexOf('#') !== -1) {
	        href = dom.playlist.getElementsByTagName('a')[0].href;
	      }

	      if (!soundObject) {
	        soundObject = makeSound(href);
	      }

	      soundObject.togglePause();

	    },

	    next: function(/* e */) {

	      var item, lastIndex;

          // special case: clear "play next" timeout, if one exists.
          if (playlistController.data.timer) {
          	window.clearTimeout(playlistController.data.timer);
          	playlistController.data.timer = null;
          }

          lastIndex = playlistController.data.selectedIndex;

	      item = playlistController.getNext(true);

          // don't play the same item again
	      if (item && playlistController.data.selectedIndex !== lastIndex) {
	        playLink(item.getElementsByTagName('a')[0]);
	      }

	    },

	    prev: function(/* e */) {

	      var item, lastIndex;

          lastIndex = playlistController.data.selectedIndex;

	      item = playlistController.getPrevious();

          // don't play the same item again
	      if (item && playlistController.data.selectedIndex !== lastIndex) {
	        playLink(item.getElementsByTagName('a')[0]);
	      }

	    },

	    shuffle: function(e) {
	      var target = e.target || e.srcElement;
	      if (!utils.css.has(target.parentNode, css.disabled)) {
	        // toggle
	        utils.css.toggle(target.parentNode, css.active);
	        playlistController.data.shuffleMode = !playlistController.data.shuffleMode;
	      }
	    },

	    repeat: function(e) {
	      var target = e.target || e.srcElement;
	      if (!utils.css.has(target, css.disabled)) {
	        utils.css.toggle(target.parentNode, css.active);
	        playlistController.data.loopMode = !playlistController.data.loopMode;
	      }
	    },

	    menu: function(/* e */) {

	      var isOpen;

	      isOpen = utils.css.toggle(dom.o, 'playlist-open');

	      // playlist
	      dom.playlistContainer.style.height = (isOpen ? dom.playlistContainer.scrollHeight : 0) + 'px';

	    },

	    adjustVolume: function(e) {

	      var backgroundSize,
	          backgroundMargin,
	          pixelMargin,
	          target,
	          value,
	          volume;

	      value = 0;

	      target = dom.volume;

	      // based on getStyle() result
	      backgroundSize = actionData.volume.backgroundSize;

	      // figure out spacing around background image based on background size, eg. 60% background size.
	      backgroundSize = 100 - backgroundSize;

	      // 60% wide means 20% margin on each side.
	      backgroundMargin = backgroundSize / 2;

	      // relative position of mouse over element
	      value = Math.max(0, Math.min(1, (e.clientX - actionData.volume.x) / actionData.volume.width));

	      target.style.clip = 'rect(0px, ' + (actionData.volume.width * value) + 'px, ' + actionData.volume.height + 'px, ' + (actionData.volume.width * (backgroundMargin/100)) + 'px)';

	      // determine logical volume, including background margin
	      pixelMargin = ((backgroundMargin/100) * actionData.volume.width);

	      volume = Math.max(0, Math.min(1, ((e.clientX - actionData.volume.x) - pixelMargin) / (actionData.volume.width - (pixelMargin*2))));

	      // set volume
	      if (soundObject) {
	        soundObject.setVolume(volume * 100);
	      }

	      return utils.events.preventDefault(e);

	    },

	    releaseVolume: function(/* e */) {

	      utils.events.remove(document, 'mousemove', actions.adjustVolume);
	      utils.events.remove(document, 'mouseup', actions.releaseVolume);

	    }/*,

	    volume: function(e) {

	      if (e.type === 'mousedown') {

	        utils.events.add(document, 'mousemove', actions.adjustVolume);

	        return utils.events.preventDefault(e);

	      } else if (e.type === 'mouseup') {

	        utils.events.remove(document, 'mousemove', actions.adjustVolume);

	      }

	    }*/

	  };

	  init();

  };

  // ---

  // expose to global
  window.sm2BarPlayers = players;
  window.SM2BarPlayer = Player;

}(window));