From e2013a34f2207f4049897cafbafac2d4c5fb3223 Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sat, 14 Mar 2015 23:03:04 -0600 Subject: [PATCH 1/7] Copied latest bar-ui.js code Code from https://github.com/scottschiller/SoundManager2/blob/3c8cc67f3cbb421753b9 b4114ece33de3097e64e/demo/bar-ui/script/bar-ui.js --- demo/bar-ui/script/bar-ui.js | 2865 ++++++++++++++++++---------------- 1 file changed, 1479 insertions(+), 1386 deletions(-) diff --git a/demo/bar-ui/script/bar-ui.js b/demo/bar-ui/script/bar-ui.js index 554bc793..a7edf806 100755 --- a/demo/bar-ui/script/bar-ui.js +++ b/demo/bar-ui/script/bar-ui.js @@ -1,1387 +1,1480 @@ -/*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 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: '' - }; - - 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
  • 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 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 or ), 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
  • 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 = '
    • ' + item.innerHTML.replace(extras.loadFailedCharacter, '') + '
    '; - - if (dom.playlistTarget.getElementsByTagName('li')[0].scrollWidth > dom.playlistTarget.offsetWidth) { - // this item can use , in fact. - dom.playlistTarget.innerHTML = '
    • ' + item.innerHTML + '
    '; - } - - } - - function makeSound(url) { - - var sound = soundManager.createSound({ - - 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 = '
    • ' + item.innerHTML + '
    '; - - if (extras.loadFailedCharacter) { - dom.playlistTarget.innerHTML = dom.playlistTarget.innerHTML.replace('
  • ' ,'
  • ' + 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 , 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 , eg., etc. Try to find the parent . - - 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; - +/*jslint plusplus: true, white: true, nomen: true */ +/* global soundManager, document, console, window */ + +(function(window) { + + /** + * SoundManager 2: "Bar UI" player + * Copyright (c) 2007, Scott Schiller. All rights reserved. + * http://www.schillmania.com/projects/soundmanager2/ + * Code provided under BSD license. + * http://schillmania.com/projects/soundmanager2/license.txt + */ + + "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 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 an array, return the last item. + if (results && results.length) { + return results[results.length-1]; + } + + // handle "not found" case + return results && results.length === 0 ? null : 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, defaultVolume, exports; + + 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: '' + }; + + 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
  • 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 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 or ), 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() { + + // inherit the default SM2 volume + defaultVolume = soundManager.defaultOptions.volume; + + 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
  • 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 = '
    • ' + item.innerHTML.replace(extras.loadFailedCharacter, '') + '
    '; + + if (dom.playlistTarget.getElementsByTagName('li')[0].scrollWidth > dom.playlistTarget.offsetWidth) { + // this item can use , in fact. + dom.playlistTarget.innerHTML = '
    • ' + item.innerHTML + '
    '; + } + + } + + function makeSound(url) { + + var sound = soundManager.createSound({ + + url: url, + + volume: defaultVolume, + + 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 = '
    • ' + item.innerHTML + '
    '; + + if (extras.loadFailedCharacter) { + dom.playlistTarget.innerHTML = dom.playlistTarget.innerHTML.replace('
  • ' ,'
  • ' + 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 getActionData(target) { + + // DOM measurements for volume slider + + if (!target) { + return false; + } + + 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; + } + + } + + function handleMouseDown(e) { + + var links, + target; + + target = e.target || e.srcElement; + + if (isRightClick(e)) { + return true; + } + + // normalize to , 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 + + getActionData(target); + + 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 , eg., etc. Try to find the parent . + + 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. + if (sound._iO && sound._iO.whileplaying) { + 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'); + + // measure volume control dimensions + if (dom.volume) { + getActionData(dom.volume); + } + + 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; + + if (e && e.target) { + + target = e.target || e.srcElement; + + href = target.href; + + } + + // haaaack - if null due to no event, OR '#' due to play/pause link, get first link from playlist + if (!href || href.indexOf('#') !== -1) { + href = dom.playlist.getElementsByTagName('a')[0].href; + } + + if (!soundObject) { + soundObject = makeSound(href); + } + + soundObject.togglePause(); + + }, + + pause: function() { + + if (soundObject && soundObject.readyState) { + soundObject.pause(); + } + + }, + + resume: function() { + + if (soundObject && soundObject.readyState) { + soundObject.resume(); + } + + }, + + stop: function() { + + // just an alias for pause, really. + // don't actually stop because that will mess up some UI state, i.e., dragging the slider. + return actions.pause(); + + }, + + 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) { + + // NOTE: not implemented yet. + + var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.shuffle')); + + if (target && !utils.css.has(target, css.disabled)) { + utils.css.toggle(target.parentNode, css.active); + playlistController.data.shuffleMode = !playlistController.data.shuffleMode; + } + + }, + + repeat: function(e) { + + var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.repeat')); + + if (target && !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 backgroundMargin, + pixelMargin, + target, + value, + volume; + + value = 0; + + target = dom.volume; + + // based on getStyle() result + // figure out spacing around background image based on background size, eg. 60% background size. + // 60% wide means 20% margin on each side. + backgroundMargin = (100 - actionData.volume.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)))) * 100; + + // set volume + if (soundObject) { + soundObject.setVolume(volume); + } + + defaultVolume = volume; + + return utils.events.preventDefault(e); + + }, + + releaseVolume: function(/* e */) { + + utils.events.remove(document, 'mousemove', actions.adjustVolume); + utils.events.remove(document, 'mouseup', actions.releaseVolume); + + }, + + setVolume: function(volume) { + + // set volume (0-100) and update volume slider UI. + + var backgroundSize, + backgroundMargin, + backgroundOffset, + pixelMargin, + target, + from, + to; + + if (volume === undefined || isNaN(volume)) { + return; + } + + if (dom.volume) { + + 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. + // 60% wide means 20% margin on each side. + backgroundMargin = (100 - backgroundSize) / 2; + + // margin as pixel value relative to width + backgroundOffset = actionData.volume.width * (backgroundMargin/100); + + from = backgroundOffset; + to = from + ((actionData.volume.width - (backgroundOffset*2)) * (volume/100)); + + target.style.clip = 'rect(0px, ' + to + 'px, ' + actionData.volume.height + 'px, ' + from + 'px)'; + + } + + // apply volume to sound, as applicable + if (soundObject) { + soundObject.setVolume(volume); + } + + defaultVolume = volume; + + } + + }; + + init(); + + exports = { + actions: actions, + dom: dom, + playlistController: playlistController + }; + + return exports; + + }; + + // --- + + // expose to global + window.sm2BarPlayers = players; + window.SM2BarPlayer = Player; + }(window)); \ No newline at end of file From d15ba16b1a4e52395298567a0113134b0b2c24b0 Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sat, 14 Mar 2015 23:19:31 -0600 Subject: [PATCH 2/7] Added utils.events.dispatch utils.events.dispatch creates a CustomEvent We can use this to dispatch events from certain places inside bar-ui.js so that external scripts may listen for that event and act accordingly --- demo/bar-ui/script/bar-ui.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/demo/bar-ui/script/bar-ui.js b/demo/bar-ui/script/bar-ui.js index a7edf806..cb1ed82b 100755 --- a/demo/bar-ui/script/bar-ui.js +++ b/demo/bar-ui/script/bar-ui.js @@ -292,7 +292,31 @@ events: (function() { - var add, remove, preventDefault; + var dispatch, add, remove, preventDefault; + + dispatch = function(evtName, custom) { + custom = typeof custom !== 'undefined' ? custom : null; + + //how we can use dispatch inside bar-ui.js: + // utils.events.dispatch('sm2-bar-ui-select', { 'detail': 0 }); + // + //how to listen for events in an external script: + // function evtHandler(e) { + // log('The value is: ' + e.detail); + // } + // var evtName = 'sm2-bar-ui-setTitle'; + // if (window.addEventListener) { + // window.addEventListener(evtName, evtHandler, false); + // } else { + // window.attachEvent('on' + evtName, evtHandler); + // } + + // create new event with the name we give + var event = new CustomEvent(evtName, custom); + + // Dispatch the event. + return window.dispatchEvent(event); + }; add = function(o, evtName, evtHandler) { // return an object with a convenient detach method. @@ -326,6 +350,7 @@ }; return { + dispatch: dispatch, add: add, preventDefault: preventDefault, remove: remove @@ -758,6 +783,8 @@ // if this is an
  • with an inner link, grab and use the text from that. var links = item.getElementsByTagName('a'); + utils.events.dispatch('sm2-bar-ui-setTitle', {'item': item}); + if (links.length) { item = links[0]; } From 75709726fe700ceb143aa0cfb40751ac1317e10a Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sun, 15 Mar 2015 03:33:12 -0600 Subject: [PATCH 3/7] Reverted from CustomEvent to standard Event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reverted from CustomEvent to standard Event for IE compatibility as well as simplicity. There was a bug in the properties I passed as well, but I’m not going to get into it and I wasn’t able to properly pass custom event details for some reason anyways. This works just fine. --- demo/bar-ui/script/bar-ui.js | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/demo/bar-ui/script/bar-ui.js b/demo/bar-ui/script/bar-ui.js index cb1ed82b..bb6303bf 100755 --- a/demo/bar-ui/script/bar-ui.js +++ b/demo/bar-ui/script/bar-ui.js @@ -294,25 +294,9 @@ var dispatch, add, remove, preventDefault; - dispatch = function(evtName, custom) { - custom = typeof custom !== 'undefined' ? custom : null; - - //how we can use dispatch inside bar-ui.js: - // utils.events.dispatch('sm2-bar-ui-select', { 'detail': 0 }); - // - //how to listen for events in an external script: - // function evtHandler(e) { - // log('The value is: ' + e.detail); - // } - // var evtName = 'sm2-bar-ui-setTitle'; - // if (window.addEventListener) { - // window.addEventListener(evtName, evtHandler, false); - // } else { - // window.attachEvent('on' + evtName, evtHandler); - // } - - // create new event with the name we give - var event = new CustomEvent(evtName, custom); + dispatch = function(evtName) { + // create new event with the name we give + var event = new Event(evtName); // Dispatch the event. return window.dispatchEvent(event); @@ -783,7 +767,7 @@ // if this is an
  • with an inner link, grab and use the text from that. var links = item.getElementsByTagName('a'); - utils.events.dispatch('sm2-bar-ui-setTitle', {'item': item}); + utils.events.dispatch('sm2-bar-ui-setTitle'); if (links.length) { item = links[0]; @@ -796,7 +780,6 @@ // this item can use , in fact. dom.playlistTarget.innerHTML = '
    • ' + item.innerHTML + '
    '; } - } function makeSound(url) { @@ -1391,6 +1374,8 @@ // playlist dom.playlistContainer.style.height = (isOpen ? dom.playlistContainer.scrollHeight : 0) + 'px'; + utils.events.dispatch('sm2-bar-ui-menu'); + }, adjustVolume: function(e) { @@ -1504,4 +1489,4 @@ window.sm2BarPlayers = players; window.SM2BarPlayer = Player; -}(window)); \ No newline at end of file +}(window)); From 00a67c9b331565f049ec80bea1efecc48514f0c6 Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sun, 15 Mar 2015 04:18:44 -0600 Subject: [PATCH 4/7] export setTitle allow setTitle to be accessed via window.sm2BarPlayers[0].setTitle(item); --- demo/bar-ui/script/bar-ui.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/bar-ui/script/bar-ui.js b/demo/bar-ui/script/bar-ui.js index bb6303bf..66ae3cdb 100755 --- a/demo/bar-ui/script/bar-ui.js +++ b/demo/bar-ui/script/bar-ui.js @@ -1476,7 +1476,8 @@ exports = { actions: actions, dom: dom, - playlistController: playlistController + playlistController: playlistController, + setTitle: setTitle }; return exports; From 86bab9153206424e9e5077e506b4cbd6ad76aa78 Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sun, 15 Mar 2015 15:48:33 -0600 Subject: [PATCH 5/7] Merge with master/scottschiller --- demo/bar-ui/script/bar-ui.js | 1498 +--------------------------------- 1 file changed, 1 insertion(+), 1497 deletions(-) diff --git a/demo/bar-ui/script/bar-ui.js b/demo/bar-ui/script/bar-ui.js index 5400372e..bc29fae0 100755 --- a/demo/bar-ui/script/bar-ui.js +++ b/demo/bar-ui/script/bar-ui.js @@ -1,1498 +1,3 @@ -<<<<<<< HEAD -/*jslint plusplus: true, white: true, nomen: true */ -/* global soundManager, document, console, window */ - -(function(window) { - - /** - * SoundManager 2: "Bar UI" player - * Copyright (c) 2007, Scott Schiller. All rights reserved. - * http://www.schillmania.com/projects/soundmanager2/ - * Code provided under BSD license. - * http://schillmania.com/projects/soundmanager2/license.txt - */ - - "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 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 an array, return the last item. - if (results && results.length) { - return results[results.length-1]; - } - - // handle "not found" case - return results && results.length === 0 ? null : 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 dispatch, add, remove, preventDefault; - - dispatch = function(evtName) { - // create new event with the name we give - var event = new Event(evtName); - - // Dispatch the event. - return window.dispatchEvent(event); - }; - - 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 { - dispatch: dispatch, - 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, defaultVolume, exports; - - 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: '' - }; - - 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
  • 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 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 or ), 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() { - - // inherit the default SM2 volume - defaultVolume = soundManager.defaultOptions.volume; - - 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
  • with an inner link, grab and use the text from that. - var links = item.getElementsByTagName('a'); - - utils.events.dispatch('sm2-bar-ui-setTitle'); - - if (links.length) { - item = links[0]; - } - - // remove any failed character sequence, also - dom.playlistTarget.innerHTML = '
    • ' + item.innerHTML.replace(extras.loadFailedCharacter, '') + '
    '; - - if (dom.playlistTarget.getElementsByTagName('li')[0].scrollWidth > dom.playlistTarget.offsetWidth) { - // this item can use , in fact. - dom.playlistTarget.innerHTML = '
    • ' + item.innerHTML + '
    '; - } - } - - function makeSound(url) { - - var sound = soundManager.createSound({ - - url: url, - - volume: defaultVolume, - - 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 = '
    • ' + item.innerHTML + '
    '; - - if (extras.loadFailedCharacter) { - dom.playlistTarget.innerHTML = dom.playlistTarget.innerHTML.replace('
  • ' ,'
  • ' + 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 getActionData(target) { - - // DOM measurements for volume slider - - if (!target) { - return false; - } - - 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; - } - - } - - function handleMouseDown(e) { - - var links, - target; - - target = e.target || e.srcElement; - - if (isRightClick(e)) { - return true; - } - - // normalize to , 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 - - getActionData(target); - - 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 , eg., etc. Try to find the parent . - - 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. - if (sound._iO && sound._iO.whileplaying) { - 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'); - - // measure volume control dimensions - if (dom.volume) { - getActionData(dom.volume); - } - - 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; - - if (e && e.target) { - - target = e.target || e.srcElement; - - href = target.href; - - } - - // haaaack - if null due to no event, OR '#' due to play/pause link, get first link from playlist - if (!href || href.indexOf('#') !== -1) { - href = dom.playlist.getElementsByTagName('a')[0].href; - } - - if (!soundObject) { - soundObject = makeSound(href); - } - - soundObject.togglePause(); - - }, - - pause: function() { - - if (soundObject && soundObject.readyState) { - soundObject.pause(); - } - - }, - - resume: function() { - - if (soundObject && soundObject.readyState) { - soundObject.resume(); - } - - }, - - stop: function() { - - // just an alias for pause, really. - // don't actually stop because that will mess up some UI state, i.e., dragging the slider. - return actions.pause(); - - }, - - 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) { - - // NOTE: not implemented yet. - - var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.shuffle')); - - if (target && !utils.css.has(target, css.disabled)) { - utils.css.toggle(target.parentNode, css.active); - playlistController.data.shuffleMode = !playlistController.data.shuffleMode; - } - - }, - - repeat: function(e) { - - var target = (e ? e.target || e.srcElement : utils.dom.get(dom.o, '.repeat')); - - if (target && !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'; - - utils.events.dispatch('sm2-bar-ui-menu'); - - }, - - adjustVolume: function(e) { - - var backgroundMargin, - pixelMargin, - target, - value, - volume; - - value = 0; - - target = dom.volume; - - // based on getStyle() result - // figure out spacing around background image based on background size, eg. 60% background size. - // 60% wide means 20% margin on each side. - backgroundMargin = (100 - actionData.volume.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)))) * 100; - - // set volume - if (soundObject) { - soundObject.setVolume(volume); - } - - defaultVolume = volume; - - return utils.events.preventDefault(e); - - }, - - releaseVolume: function(/* e */) { - - utils.events.remove(document, 'mousemove', actions.adjustVolume); - utils.events.remove(document, 'mouseup', actions.releaseVolume); - - }, - - setVolume: function(volume) { - - // set volume (0-100) and update volume slider UI. - - var backgroundSize, - backgroundMargin, - backgroundOffset, - pixelMargin, - target, - from, - to; - - if (volume === undefined || isNaN(volume)) { - return; - } - - if (dom.volume) { - - 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. - // 60% wide means 20% margin on each side. - backgroundMargin = (100 - backgroundSize) / 2; - - // margin as pixel value relative to width - backgroundOffset = actionData.volume.width * (backgroundMargin/100); - - from = backgroundOffset; - to = from + ((actionData.volume.width - (backgroundOffset*2)) * (volume/100)); - - target.style.clip = 'rect(0px, ' + to + 'px, ' + actionData.volume.height + 'px, ' + from + 'px)'; - - } - - // apply volume to sound, as applicable - if (soundObject) { - soundObject.setVolume(volume); - } - - defaultVolume = volume; - - } - - }; - - init(); - - exports = { - actions: actions, - dom: dom, - playlistController: playlistController, - setTitle: setTitle - }; - - return exports; - - }; - - // --- - - // expose to global - window.sm2BarPlayers = players; - window.SM2BarPlayer = Player; - -}(window)); -======= /*jslint plusplus: true, white: true, nomen: true */ /* global soundManager, document, console, window */ @@ -3091,5 +1596,4 @@ window.sm2BarPlayers = players; window.SM2BarPlayer = Player; -}(window)); ->>>>>>> scottschiller/master +}(window)); From 5df0c08e34f6f7b6ccdb90d40f6f5a26ad5d0028 Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sun, 15 Mar 2015 22:50:48 -0600 Subject: [PATCH 6/7] Added dispatch function to utils.events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added dispatch function to utils.events and then dispatched a couple events from 2 key locations I’m currently using. --- .gitignore | 2 ++ demo/bar-ui/script/bar-ui.js | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6d0aa29b..02b84326 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ make-rollups.sh tmp .build.properties download + +*.swp diff --git a/demo/bar-ui/script/bar-ui.js b/demo/bar-ui/script/bar-ui.js index bc29fae0..711ee64f 100755 --- a/demo/bar-ui/script/bar-ui.js +++ b/demo/bar-ui/script/bar-ui.js @@ -292,7 +292,15 @@ events: (function() { - var add, remove, preventDefault; + var dispatch, add, remove, preventDefault; + + dispatch = function(evtName) { + // create new event with the name we give + var event = new Event(evtName); + + // Dispatch the event. + return window.dispatchEvent(event); + }; add = function(o, evtName, evtHandler) { // return an object with a convenient detach method. @@ -326,6 +334,7 @@ }; return { + dispatch: dispatch, add: add, preventDefault: preventDefault, remove: remove @@ -809,6 +818,8 @@ // if this is an
  • with an inner link, grab and use the text from that. var links = item.getElementsByTagName('a'); + utils.events.dispatch('sm2-bar-ui-setTitle'); + if (links.length) { item = links[0]; } @@ -1462,6 +1473,8 @@ // playlist dom.playlistContainer.style.height = (isOpen ? dom.playlistContainer.scrollHeight : 0) + 'px'; + utils.events.dispatch('sm2-bar-ui-menu'); + }, adjustVolume: function(e) { From 0f800191af640f48f7e90d5c470718bfd21c0fa8 Mon Sep 17 00:00:00 2001 From: MistahWrite Date: Sun, 15 Mar 2015 23:39:14 -0600 Subject: [PATCH 7/7] Renamed some style elements previous and menu interfered with the theme I have on Wordpress so I renamed them to something more specific --- demo/bar-ui/css/bar-ui.css | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/demo/bar-ui/css/bar-ui.css b/demo/bar-ui/css/bar-ui.css index 063d73c1..f5476fe5 100755 --- a/demo/bar-ui/css/bar-ui.css +++ b/demo/bar-ui/css/bar-ui.css @@ -549,25 +549,25 @@ background-image: none, url(../image/icomoon/entypo-25px-000000/SVG/volume.svg); } -.menu { +.sm2-menu-button { background-image: url(../image/icomoon/entypo-25px-ffffff/PNG/list2.png); background-image: none, url(../image/icomoon/entypo-25px-ffffff/SVG/list2.svg); background-size: 58%; background-position: 54% 51%; } -.previous { +.sm2-previous { background-image: url(../image/icomoon/entypo-25px-ffffff/PNG/first.png); background-image: none, url(../image/icomoon/entypo-25px-ffffff/SVG/first.svg); } -.next { +.sm2-next { background-image: url(../image/icomoon/entypo-25px-ffffff/PNG/last.png); background-image: none, url(../image/icomoon/entypo-25px-ffffff/SVG/last.svg); } -.previous, -.next { +.sm2-previous, +.sm2-next { background-size: 49.5%; background-position: 50% 50%; }