/* jsAMP - V0.9a.20080331 - "Technology Preview" - DEMO ONLY --------------------------------------------------------- An MP3 player implementation using the SoundManager 2 API (And a sanity QA / test suite for the core API calls. :D) http://www.schillmania.com/projects/soundmanager2/ --------------------------------------------------------- * GENERAL DISCLAIMER: jsAMP is UNSUPPORTED DEMO CODE. * jsAMP is provided "as-is" and as an example application using the API functionality provided by SoundManager 2. (It's also a dev. sanity QA check / API test suite.) I don't recommend throwing it on your band/label's site expecting it to "just work" - you have been warned. ;) You are welcome to use this in your own projects, but be aware jsAMP may be buggy, use at your own risk etc. If you are looking for a JS/DHTML/Flash MP3 player, check the related projects section of the SM2 project page for other resources. --------------------------------------------------------- */ function SMUtils() { var self = this; this.isSafari = navigator.userAgent.match(/safari/); this.isMac = navigator.platform.match(/mac/); this.isIE = (navigator.appVersion.match(/MSIE/) && !navigator.userAgent.match(/Opera/)); this.isNewIE = (this.isIE && !this.isMac && (!navigator.userAgent.match(/MSIE (5|6)/))); this.isOldIE = (this.isIE && !this.isNewIE); this.$ = function(sID) { return document.getElementById(sID); } this.isChildOf = function(oChild,oParent) { while (oChild.parentNode && oChild != oParent) { oChild = oChild.parentNode; } return (oChild == oParent); } this.addEventHandler = function(o,evtName,evtHandler) { typeof(attachEvent)=='undefined'?o.addEventListener(evtName,evtHandler,false):o.attachEvent('on'+evtName,evtHandler); } this.removeEventHandler = function(o,evtName,evtHandler) { typeof(attachEvent)=='undefined'?o.removeEventListener(evtName,evtHandler,false):o.detachEvent('on'+evtName,evtHandler); } this.classContains = function(o,cStr) { return (typeof(o.className)!='undefined'?o.className.indexOf(cStr)+1:false); } this.addClass = function(o,cStr) { if (!o) return false; // safety net if (self.classContains(o,cStr)) return false; o.className = (o.className?o.className+' ':'')+cStr; } this.removeClass = function(o,cStr) { if (!o) return false; // safety net if (!self.classContains(o,cStr)) return false; o.className = o.className.replace(new RegExp('( '+cStr+')|('+cStr+')','g'),''); } this.getElementsByClassName = function(className,tagNames,oParent) { var doc = (oParent||document); var matches = []; var i,j; var nodes = []; if (typeof(tagNames)!='undefined' && typeof(tagNames)!='string') { for (i=tagNames.length; i--;) { if (!nodes || !nodes[tagNames[i]]) { nodes[tagNames[i]] = doc.getElementsByTagName(tagNames[i]); } } } else if (tagNames) { nodes = doc.getElementsByTagName(tagNames); } else { nodes = doc.all||doc.getElementsByTagName('*'); } if (typeof(tagNames)!='string') { for (i=tagNames.length; i--;) { for (j=nodes[tagNames[i]].length; j--;) { if (self.classContains(nodes[tagNames[i]][j],className)) { matches[matches.length] = nodes[tagNames[i]][j]; } } } } else { for (i=0; iself.xMaxLoaded) { x = self.xMaxLoaded; } else if (x=self.tween.length-1) { if (self.frame>=self.tween.length-1) { self.active = false; self.frame = 0; if (self._oncomplete) self._oncomplete(); return false; } return true; } this.doScrub = function(t) { if (self.oParent.paused) return false; if (self.oParent.options.scrubThrottle) { if (!self.timer) self.timer = setTimeout(self.scrub,t||20); } else { self.scrub(); } } this.scrub = function() { self.timer = null; self.oParent.onUserSetSlideValue(self.x) } this.randomize = function() { self.slide(self.x,parseInt(Math.random()*self.xMax)); } this.getTimeEstimate = function(oSound) { // try to estimate song length within first 128 KB (or total bytes), updating n times var byteCeiling = Math.min(1048576||oSound.bytes); var samples = (byteCeiling==oSound.bytes?2:4); var milestone = Math.floor(oSound.bytesLoaded/byteCeiling*samples); if (oSound.bytesLoaded>byteCeiling && self.gotTimeEstimate>0) return false; if (self.gotTimeEstimate == milestone) return false; self.gotTimeEstimate = milestone; self.setMetaData(oSound); } this.getTime = function(nMSec,bAsString) { // convert milliseconds to mm:ss, return as object literal or string var nSec = Math.floor(nMSec/1000); var min = Math.floor(nSec/60); var sec = nSec-(min*60); if (min == 0 && sec == 0) return null; // return 0:00 as null return (bAsString?(min+':'+(sec<10?'0'+sec:sec)):{'min':min,'sec':sec}); } this.updateTime = function(nMSec) { // update "current playing" time self.lastTime = nMSec; self.oTime.innerHTML = (self.getTime(nMSec,true)||'0:00'); } this.setTitle= function(sTitle) { // used in the absence of ID3 info self.oTitle.innerHTML = unescape(sTitle); self.titleString = unescape(sTitle); self.refreshScroll(); } this.isEmpty = function(o) { return (typeof o == 'undefined' || o == null || o == 'null' || (typeof o == 'string' && o.toLowerCase() == 'n/a' || o.toLowerCase == 'undefined')); } self.setMetaData = function(oSound) { // get id3 data and populate according to formatting string (%artist - %title [%album] etc.) var friendlyAttrs = { // ID3V1 inherits from ID3V2 if populated 'title': 'songname', // songname/TIT2 'artist': 'artist', // artist/TPE1 'album': 'album', // album/TALB 'track': 'track', // track/TRCK 'year': 'year', // year/TYER 'genre': 'genre', // genre/TCON 'comment': 'comment', // comment/COMM 'url': 'WXXX' } var sTime = self.getTime(oSound.durationEstimate,true); sTime = (sTime && !oSound.loaded?'~':'')+sTime; var metaAttrs = { // custom attributes taken directly from sound data 'time': sTime // get time as mm:ss } // get normalised data, build string, replace var sData = self.sFormat; // eg. %{artist} - %{title} var data = null; var useID3 = (!self.isEmpty(oSound.id3.songname) && !self.isEmpty(oSound.id3.artist)); // artist & title must be present to consider using ID3 for (var attr in friendlyAttrs) { data = oSound.id3[friendlyAttrs[attr]]; if (self.isEmpty(data)) data = '!null!'; sData = sData.replace('\%\{'+attr+'\}',data); } for (var attr in metaAttrs) { data = metaAttrs[attr]; if (self.isEmpty(data)) data = '!null!'; sData = sData.replace('\%\{'+attr+'\}',data); } // remove any empty/null fields var aData = sData.split(' '); for (var i=aData.length; i--;) { if (aData[i].indexOf('!null!')+1) aData[i] = null; } var sMetaData = (useID3?unescape(aData.join(' ')):unescape(self.oParent.oPlaylist.getCurrentItem().userTitle)+(!self.isEmpty(metaAttrs.time)?' ('+metaAttrs.time+')':'')).replace(/\s+/g,' '); self.oTitle.innerHTML = sMetaData; self.titleString = sMetaData; self.oParent.oPlaylist.getCurrentItem().setTooltip(sMetaData); self.refreshScroll(); } this.setLoadingProgress = function(nPercentage) { // soundManager._writeDebug('setLoadingProgress(): '+nPercentage); self.percentLoaded = nPercentage; self.xMaxLoaded = self.percentLoaded*self.xMax; self.oProgress.style.width = parseInt(nPercentage*self.barWidth)+'px'; } this.setLoading = function(bLoading) { if (self.isLoading == bLoading) return false; self.isLoading = bLoading; var f = bLoading?u.addClass:u.removeClass; f(self.oProgress,'loading'); self.setLoadingAnimation(bLoading); } this.setLoadingAnimation = function(bLoading) { soundManager._writeDebug('setLoadingAnimation(): '+bLoading); if (bLoading) { self.loadingTween = self.loadingTweens[0]; animator.addMethod(self.loadingAnimate); animator.addMethod(self.loadingAnimateSlide,self.loadingAnimateSlideComplete); animator.start(); } else { self.loadingTween = self.loadingTweens[1]; if (self.loadingAnimateFrame>0) { // reverse animation while active // self.loadingTween.reverse(); self.loadingAnimateFrame = (self.loadingTween.length-self.loadingAnimateFrame); } else { self.loadingTween = self.loadingTweens[1]; animator.addMethod(self.loadingAnimateSlide,self.loadingAnimateSlideComplete); } } } this.loadingAnimate = function() { var d = new Date(); if (d-self.loadingLastExec<50) return true; // throttle fps self.loadingLastExec = d; self.loadingX--; self.oProgress.style.backgroundPosition = self.loadingX+'px '+self.loadingY+'px'; return self.isLoading; } this.loadingLastExec = new Date(); this.loadingTweens = [animator.createTween(0,self.maxOpacity),animator.createTween(self.maxOpacity,0)]; this.loadingDirection = 0; this.loadingTween = this.loadingTweens[this.loadingDirection]; this.loadingAnimateFrame = 0; this.loadingAnimateSlide = function() { var d = new Date(); if (d-self.loadingLastExec<50) return true; // throttle to 20fps u.setOpacity(self.oProgress,self.loadingTween[self.loadingAnimateFrame++]); if (!self.isLoading) self.loadingAnimate(); // show update if not actively loading self.loadingLastExec = d; // updates time, prevents loadingAnimate() return (++self.loadingAnimateFrameself.coords.titleWidth) { // soundManager._writeDebug('wrapping around'); self.scrollOffset = (smUtils.isIE?5:1); } self.scrollTo(self.scrollOffset); return self.isScrolling; } this.resetScroll = function() { soundManager._writeDebug('resetScroll()'); self.scrollOffset = 0; self.scrollTo(self.scrollOffset); self.refreshDocumentTitle(0); } this.setScroll = function(bScroll) { soundManager._writeDebug('setScroll('+bScroll+')'); if (bScroll && !self.isScrolling) { soundManager._writeDebug('starting scroll'); self.isScrolling = true; animator.addMethod(self.doScroll,self.resetScroll); animator.start(); } if (!bScroll && self.isScrolling) { soundManager._writeDebug('stopping scroll'); self.isScrolling = false; } } this.titleString = ''; // for document title this.refreshScroll = function() { // self.scrollOffsetMax = 25; // self.oTitle.innerHTML.length; // soundManager._writeDebug('refreshScroll(): '+self.scrollOffsetMax); self.coords.titleWidth = self.oTitle.offsetWidth; var doScroll = (self.coords.titleWidth>self.scrollOffsetMax); if (doScroll) { var sHTML = self.oTitle.innerHTML; var dHTML = self.oDivider.innerHTML; // heh self.oTitle.innerHTML = sHTML+dHTML; self.coords.titleWidth = self.oTitle.offsetWidth; self.setScroll(doScroll); self.titleString = sHTML; self.oTitle.innerHTML = sHTML+dHTML+sHTML; } else { self.setScroll(doScroll); self.titleString = self.oTitle.innerHTML; } // if (doScroll) self.oTitle.innerHTML = (self.oTitle.innerHTML+' *** '+self.oTitle.innerHTML); // fake the "repeat" } this.reset = function() { soundManager._writeDebug('SMPlayer.reset()'); if (self.x != 0) self.moveTo(0); self.setLoadingProgress(0); self.gotTimeEstimate = 0; self.updateTime(0); self.resetScroll(); } this.destructor = function() { self.oBar.onmouseover = null; self.oBar.onmouseout = null; self.o.onmousedown = null; self.o = null; self.oV = null; self.oB.onclick = null; self.oB = null; } if (u.isIE) { // IE is lame, no :hover this.oBar.onmouseover = this.over; this.oBar.onmouseout = this.out; } if (u.isSafari) u.addClass(this.oMain,'noOpacity'); // stupid transparency tweak if (useAltFont) u.addClass(this.oMain,'altFont'); // this.setScroll(true); // test this.oSlider.onmousedown = this.down; this.oBar.onmousedown = this.barDown; this.oBar.onclick = this.barClick; // self.update(); // start scrolling, if needed self.refreshScroll(); } function Animator() { var self = this; this.timer = null; this.active = null; this.methods = []; this.tweenStep = [1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2]; this.frameCount = this.tweenStep.length; // this.lastExec = new Date(); this.start = function() { if (self.active==true) return false; self.active = true; self.timer = window.setInterval(self.animate,20); } this.stop = function() { if (self.timer) { window.clearInterval(self.timer); self.timer = null; self.active = false; } } this.reset = function() { self.methods = []; } this.addMethod = function(oMethod,oncomplete) { for (var i=self.methods.length; i--;) { if (self.methods[i] == oMethod) { if (oncomplete) { self.methods[i]._oncomplete = oncomplete; } return false; } } self.methods[self.methods.length] = oMethod; self.methods[self.methods.length-1]._oncomplete = oncomplete||null; } this.createTween = function(start,end) { var start = parseInt(start); var end = parseInt(end); var tweenStepData = self.tweenStep; var tween = [start]; var tmp = start; var diff = end-start; var j = tweenStepData.length; var isAscending = end>start; for (var i=0; iend) tween[i] = end; } else { if (tween[i]= self.items.length) nextItem = -1; return nextItem; } this.getNextItem = function() { self.index++; if (self.index>=self.items.length) { self.index = -1; // reset return false; } return true; } this.calcPreviousItem = function() { var prevItem = self.index-1; if (prevItem <0) prevItem = self.items.length-1; return prevItem; } this.getPreviousItem = function() { // self.index--; if (--self.index<0) { self.index = self.items.length-1; return false; } return true; } this.playNextItem = function() { // call getNextItem, decide what to do based on repeat/random state etc. soundManager._writeDebug('SPPlaylist.playNextItem()'); if (self.getNextItem() || self.doRepeat) { if (self.doRepeat && self.index == -1) { // did loop soundManager._writeDebug('did loop - restarting playlist'); self.index = 0; } self.play(self.index); self.setHighlight(self.index); } else { soundManager._writeDebug('SPPlaylist.playNextItem(): finished?'); // finished self.index = self.items.length-1; if (!oParent.playState) { self.play(self.index); // only start playing if currently stopped } // self.setHighlight(self.index); } } this.playPreviousItem = function() { // call getPreviousItem, decide what to do soundManager._writeDebug('SPPlaylist.playPreviousItem()'); if (self.getPreviousItem() || self.doRepeat) { // self.play(self.playlistItems[self.index].index); self.play(self.index); self.setHighlight(self.index); } else { // soundManager._writeDebug('SPPlaylist.playPreviousItem(): finished?'); self.index = 0; // if (!oParent.playState) self.play(self.playlistItems[self.index].index); // only start playing if currently stopped if (!oParent.playState) self.play(self.index); // only start playing if currently stopped self.setHighlight(self.index); } } this.setHighlight = function(i) { if (self.playlistItems[i]) self.playlistItems[i].setHighlight(); // self.index = i; if (self.lastIndex != null && self.lastIndex != i) self.removeHighlight(self.lastIndex); self.lastIndex = i; } this.removeHighlight = function(i) { if (self.playlistItems[i]) self.playlistItems[i].removeHighlight(); } this.selectItem = function(i) { self.index = i; self.setHighlight(i); } this.onItemBeforeFinish = function() { // NOTE: This could be inconsistent across systems and is not guaranteed (it's JS-based timing.) if (oParent.oSMPlayer.busy) return false; // ignore if user is scrubbing // setTimeout(self.onItemJustBeforeFinish,4800); soundManager._writeDebug('SPPlaylist.onItemBeforeFinish()'); // start preloading next track var nextItem = self.calcNextItem(); self.load(self.playlistItems[nextItem].index); } this.onItemJustBeforeFinish = function() { // compensate for JS/Flash lag to attempt seamless audio. (woah.) soundManager._writeDebug('SPPlaylist.onItemJustBeforeFinish()'); // soundManager.getSoundById(oParent.currentSound)._ignoreOnFinish = true; // prevent this sound's onfinish() from triggering next load, etc. soundManager.getSoundById(this.sID)._ignoreOnFinish = true; // prevent this sound's onfinish() from triggering next load, etc. if (this.sID == oParent.currentSound) { // just in case this method fires too late (next song already playing..) self._ignoreCurrentSound = true; // prevent current track from stopping self.playNextItem(); } } this.onItemBeforeFinishComplete = function() { // TODO: Make getting SID reference cleaner (scope to playlist item) soundManager._writeDebug('onItemBeforeFinishComplete()'); // soundManager.stop(oParent.lastSound); // soundManager.unload(oParent.lastSound); } this.onItemFinish = function() { soundManager._writeDebug('SPPlaylist.onItemFinish()'); if (this._ignoreOnFinish) { // special case for seamless playback - don't trigger next track, already done soundManager._writeDebug('sound '+this.sID+' ended with ._ignoreOnFinish=true'); this._ignoreOnFinish = false; // reset for next use return false; } oParent.setPlayState(false); // stop if (!self.getNextItem()) { self.onfinish(); } else { // self.play(self.playlistItems[self.index].index); // not needed? self.play(self.index); // not needed? self.setHighlight(self.index); } } this.onfinish = function() { // end of playlist soundManager._writeDebug('SPPlaylist.onfinish()'); oParent.onfinish(); // hacks: reset scroll and index oParent.x = 0; // haaack oParent.lastSound = oParent.currentSound; oParent.currentSound = null; self.removeHighlight(self.index); // reset highlight self.index = -1; // haaack // self.reset(); // if repeat mode, start playing next song if (self.doRepeat) self.playNextItem(); } this.show = function() { self.setDisplay(true); } this.hide = function() { self.setDisplay(); } this.toggleShuffle = function() { soundManager._writeDebug('SPPlaylist.toggleShuffle()'); self.doShuffle = !self.doShuffle; soundManager._writeDebug('shuffle: '+self.doShuffle); if (self.doShuffle) { // undo current highlight self.removeHighlight(self.index); self.shufflePlaylist(); self.playlistItems = self.playlistItemsShuffled; self.index = 0; // self.playlistItems[0].index; self.setHighlight(0); self.play(0); } else { self.index = self.playlistItems[self.index].origIndex; // restore to last unsorted position self.lastIndex = self.playlistItems[self.lastIndex].origIndex; // map new lastIndex self.playlistItems = self.playlistItemsUnsorted; } } this.toggleRepeat = function() { soundManager._writeDebug('SPPlaylist.toggleRepeat()'); self.doRepeat = !self.doRepeat; soundManager._writeDebug('repeat: '+self.doRepeat); } this.shufflePlaylist = function() { soundManager._writeDebug('SPPlaylist.shufflePlaylist()'); var p = self.playlistItemsShuffled, j = null, tmp = null, newIndex = null; for (var i=p.length; i--;) { j = parseInt(Math.random()*p.length); tmp = p[j]; p[j] = p[i]; p[i] = tmp; } } this.displayTweens = null; this.opacityTweens = [animator.createTween(90,0),animator.createTween(0,90)]; this.displayTween = null; this.opacityTween = null; this.widthTweens = null; this.widthTween = null; this.frame = 0; this.setOpacity = function(nOpacity) { // soundManager._writeDebug('spPlaylist.setOpacity('+nOpacity+')'); // u.setOpacity(self.o,nOpacity); } this.createTweens = function() { // calculate tweens var base = (smUtils.isOldIE?16:0); // IE<7 needs vertical offset for playlist. self.displayTweens = [animator.createTween(base,self.o.offsetHeight),animator.createTween(self.o.offsetHeight,base)]; self.widthTweens = [animator.createTween(self.o.offsetWidth,1),animator.createTween(1,self.o.offsetWidth)]; } this.setCoords = function(nHeight,nOpacity,nWidth) { self.o.style.marginTop = -nHeight+'px'; if (!smUtils.isIE) smUtils.setOpacity(self.o,nOpacity); // self.o.style.width = nWidth+'px'; // self.o.style.marginLeft = (parseInt((self.widthTweens[0][0]-nWidth)/2)+1)+'px'; } this.animate = function() { self.frame = Math.max(++self.frame,animator.determineFrame(self.displayLastExec,35)); // self.frame++; self.setCoords(self.displayTween[self.frame],self.opacityTween[self.frame],self.widthTween[self.frame]); // self.playlistItems[self.frame].doAnimate(1); if (self.frame>=self.displayTween.length-1) { // self.active = false; self.frame = 0; return false; } return true; } this.displayLastExec = new Date(); this.setDisplay = function(bDisplay) { soundManager._writeDebug('setDisplay()'); self.displayTween = self.displayTweens[self.isVisible?1:0]; self.opacityTween = self.opacityTweens[self.isVisible?1:0]; self.widthTween = self.widthTweens[self.isVisible?1:0]; if (self.frame>0) self.frame = self.displayTweens[0].length-self.frame; self.displayLastExec = new Date(); animator.addMethod(self.animate,self.animateComplete); animator.start(); } this.animateComplete = function() { // soundManager._writeDebug('spPlaylist.animateComplete()'); // if (self.isVisible) self.o.style.display = 'none'; } this.toggleDisplay = function() { self.isVisible = !self.isVisible; if (!self.isVisible) self.o.style.display = 'block'; self.setDisplay(self.isVisible); } this.createPlaylist = function() { for (var i=0,j=self.items.length; iSPPlaylist.searchForSoundLinks(): Error at link index '+i+' - may be caused by funny characters in URL'); // return false; } } } this.load = function(i) { soundManager._writeDebug('SPPlaylist.load('+i+')'); // start preloading a sound var sID = 'spsound'+i; var s = soundManager.getSoundById(sID,true); if (s) { // reload (preload) existing sound soundManager._writeDebug('reloading existing sound'); var thisOptions = { 'autoPlay': false, 'url': s.url, // reload original URL (assuming currently "unloaded" state) 'stream': true } s.load(thisOptions); } else { soundManager._writeDebug('preloading new sound'); soundManager.createSound({ 'id': sID, 'url': self.items[i].url, // 'onload': self.onload, 'onload': oParent.onload, 'stream': true, 'autoLoad': true, 'autoPlay': false, 'onid3': oParent.onid3, 'onplay': oParent.onplay, 'whileloading': oParent.whileloading, 'whileplaying': oParent.whileplaying, 'onbeforefinish': self.onItemBeforeFinish, 'onbeforefinishcomplete': self.onItemBeforeFinishComplete, 'onbeforefinishtime': 5000, 'onjustbeforefinish': self.onItemJustBeforeFinish, 'onjustbeforefinishtime':seamlessDelay , // 0 = do not call 'onfinish': self.onItemFinish, 'multiShot': false }); // s = soundManager.getSoundById(sID); // soundManager._writeDebug('preloaded sound load state: '+s.loaded+''); // soundManager.getSoundById(sID).disableEvents(); // prevent UI calls etc., just preload // self.setMetaData(soundManager.getSoundById(sID)); } } this.play = function(i) { // scoped to playlistItem instance if (!self.items[i]) return false; soundManager._writeDebug('SPPlaylist.play()'); // var sID = 'spsound'+self.index; // if (i==-1) i=0; // safeguard if (self.doShuffle) i = self.playlistItems[i].index; // if shuffle enabled, map to proper sound var sID = 'spsound'+i; var exists = false; if (oParent.currentSound) { if (!self._ignoreCurrentSound) { soundManager._writeDebug('stopping current sound'); soundManager.stop(oParent.currentSound); soundManager.unload(oParent.currentSound); } else { soundManager._writeDebug('allowing current sound to finish'); self._ignoreCurrentSound = false; } } if (!soundManager.getSoundById(sID,true)) { soundManager._writeDebug('creating sound '+sID); soundManager.createSound({ 'id': sID, 'url': self.items[i].url, // 'onload': self.onload, 'onload': oParent.onload, 'stream': true, 'autoPlay': false, 'onid3': oParent.onid3, 'onplay': oParent.onplay, 'whileloading': oParent.whileloading, 'whileplaying': oParent.whileplaying, 'onbeforefinish': self.onItemBeforeFinish, 'onbeforefinishcomplete': self.onItemBeforeFinishComplete, 'onbeforefinishtime': 5000, 'onjustbeforefinish': self.onItemJustBeforeFinish, 'onjustbeforefinishtime':seamlessDelay, 'onfinish': self.onItemFinish, 'multiShot': false }); } else { // sound already exists - preload or replay use cases exists = true; soundManager._writeDebug('sound id '+sID+' already exists (preload/reuse case)'); } soundManager._writeDebug('Refreshing sound details'); oParent.refreshDetails(sID); oParent.lastSound = oParent.currentSound; oParent.currentSound = sID; oParent.reset(); // ensure slider starts at 0 oParent.setLoading(true); soundManager.play(sID); oParent.setPlayState(true); // apply URL hash if (oParent.options.allowBookmarking) window.location.hash = 'track='+encodeURI(self.items[i].url.substr(self.items[i].url.lastIndexOf('/')+1)); if (exists) { var s = soundManager.getSoundById(sID); oParent.setMetaData(s); if (s.loaded) { // already loaded before playing started - calculate time estimates, re-call onload() now oParent.onload.apply(s); } } } this.init = function() { self.o = document.getElementById('playlist-template'); // set width to parent self.o.style.width = (parseInt(oParent.oSMPlayer.oMain.offsetWidth)-2)+'px'; // smUtils.getElementsByClassName('sm2playlist-box','div',self.o)[0].style.width = '100px'; } this.loadFromHash = function() { // given a hash, match an MP3 URL and play it. if (!oParent.options.allowBookmarking) return false; var hash = oParent.options.hashPrefix; var hashOffset = hash.length+1; var i = (window.location.hash.indexOf(hash)); if (i==-1) return false; var url = decodeURI(window.location.hash.substr(hashOffset)); soundManager._writeDebug('loadFromHasn(): searching for '+url); var index = self.findItemByURL(encodeURI(url)); if (index == -1) { soundManager._writeDebug('trying alternate search..'); index = self.findItemByURL(escape(url)); } if (index != -1) { soundManager._writeDebug('loadFromHash(): found index '+index+' ('+url+')'); self.selectItem(index); self.play(index); smUtils.addEventHandler(window,'beforeunload',self.removeHash); } else { soundManager._writeDebug('loadFromHash(): no match found'); } } this.removeHash = function() { // experimental - probably won't work in any good browsers (eg. Firefox) try { window.location.hash = ''; // prevent reload from maintaining current state } catch(e) { // oh well } } this.findItemByURL = function(sURL) { for (var i=self.items.length; i--;) { if (self.items[i].url.indexOf(sURL)!=-1) { return i; } } return -1; } this.init(); } function SPPLaylistItem(oLink,oPlaylist,nIndex) { var self = this; var oParent = oPlaylist; this.index = nIndex; this.origIndex = nIndex; this.userTitle = oLink.innerHTML; var sURL = oParent.items[this.index].url; this.o = document.createElement('li'); if (nIndex%2==0) this.o.className = 'alt'; // striping this.o.innerHTML = ''; this.o.getElementsByTagName('span')[0].innerHTML = this.userTitle; this.setHighlight = function() { smUtils.addClass(self.o,'highlight'); } this.removeHighlight = function() { smUtils.removeClass(self.o,'highlight'); } this.setTooltip = function(sHTML) { self.o.title = sHTML; } this.onclick = function() { if (oParent.doShuffle) soundPlayer.toggleShuffle(); // disable shuffle, if on (should be oParent.oParent too, ideally) oParent.selectItem(self.index); oParent.play(self.index); return false; } this.init = function() { // append self.o somewhere // oParent.o.appendChild(self.o); document.getElementById('playlist-template').getElementsByTagName('ul')[0].appendChild(self.o); self.o.onclick = self.onclick; } this.init(); } function SoundPlayer() { var self = this; this.urls = []; // will get from somewhere.. this.currentSound = null; // current sound ID (offset/count) this.lastSound = null; this.oPlaylist = null; this.oSMPlayer = null; this.playState = 0; this.paused = false; this.options = { allowScrub: true, // let sound play when possible while user is dragging the slider (seeking) scrubThrottle: false, // prevent scrub update call on every mouse move while dragging - "true" may be nicer on CPU, but track will update less allowBookmarking: false, // change URL to reflect currently-playing MP3 usePageTitle: true, // change document.title (window title) to reflect track data hashPrefix: 'track=' // eg. #track=foo%20bar.mp3 } var u = smUtils; // alias this.reset = function() { // this.sliderPosition = 0; self.oSMPlayer.reset(); } this.whileloading = function() { if (this.sID != self.currentSound) return false; // "this" scoped to soundManager SMSound object instance // this.sID, this.bytesLoaded, this.bytesTotal // soundManager._writeDebug('whileLoading: '+parseInt(this.bytesLoaded/this.bytesTotal*100)+' %'); self.oSMPlayer.setLoadingProgress(Math.max(0,this.bytesLoaded/this.bytesTotal)); self.oSMPlayer.getTimeEstimate(this); } this.onload = function() { if (this.sID != self.currentSound) return false; // force slider calculation (position) update? // soundManager._writeDebug('time, estimate: '+this.duration+', '+this.durationEstimate+''); soundManager._writeDebug('soundPlayer.onload()'); self.oSMPlayer.setLoadingProgress(1); // ensure complete self.setMetaData(this); self.oSMPlayer.setLoading(false); } this.onid3 = function() { if (this.sID != self.currentSound) return false; soundManager._writeDebug('SoundPlayer.onid3()'); // update with received ID3 data self.setMetaData(this); } this.onfinish = function() { // sound reached end - reset controls, stop? // document.getElementById('controls').getElementsByTagName('dd')[0].innerHTML = 'Finished playing.'; soundManager._writeDebug('SoundPlayer.onfinish()'); self.oSMPlayer.moveToEnd(); self.setPlayState(false); } this.onplay = function() { // started playing? soundManager._writeDebug('.onplay!'); } this.whileplaying = function() { if (this.sID != self.currentSound) return false; // this.sID, this.position, this.duration // with short MP3s when loading for >1st time, this.duration can be null?? var duration = (!this.loaded?this.durationEstimate:this.duration); // use estimated duration until completely loaded if (this.position>duration) return false; // can happen when resuming from an unloaded state? var newPos = Math.floor(this.position/duration*self.oSMPlayer.xMax); if (newPos != self.oSMPlayer.x) { // newPos > self.oSMPlayer.x if (!self.oSMPlayer.busy) { self.oSMPlayer.moveTo(newPos); self.oSMPlayer.update(); } } if (Math.abs(this.position-self.oSMPlayer.lastTime)>1000) self.oSMPlayer.updateTime(this.position); } this.onUserSetSlideValue = function(nX) { // called from slider control var x = parseInt(nX); // soundManager._writeDebug('onUserSetSlideValue('+x+')'); // play sound at this position var s = soundManager.sounds[self.currentSound]; if (!s) return false; var nMsecOffset = Math.floor(x/self.oSMPlayer.xMax*s.durationEstimate); soundManager.setPosition(self.currentSound,nMsecOffset); } this.setTitle = function(sTitle) { var title = (typeof sTitle == 'undefined'?'Untitled':sTitle); self.oSMPlayer.setTitle(title); self.oSMPlayer.refreshScroll(); } this.setMetaData = function(oSound) { // pass sound to oSMPlayer self.oSMPlayer.setMetaData(oSound); } this.setLoading = function(bLoading) { self.oSMPlayer.setLoading(bLoading); } this.setPlayState = function(bPlaying) { self.playState = bPlaying; self.oSMPlayer.setPlayState(bPlaying); } this.refreshDetails = function(sID) { var id = (sID||self.currentSound); if (!id) return false; var s = soundManager.getSoundById(id); if (!s) return false; soundManager._writeDebug('refreshDetails(): got sound: '+s); // in absence of ID3, use user-provided data (URL or link text?) self.setTitle(self.oPlaylist.getCurrentItem().userTitle); } this.volumeDown = function(e) { self.oSMPlayer.volumeDown(e); } this.togglePause = function() { self.oSMPlayer.togglePause(); } this.toggleShuffle = function() { self.oSMPlayer.toggleShuffle(); } this.toggleRepeat = function() { self.oSMPlayer.toggleRepeat(); } this.toggleMute = function() { // soundManager._writeDebug('soundPlayer.toggleMute()'); self.oSMPlayer.toggleMute(); } this.togglePlaylist = function() { soundManager._writeDebug('soundPlayer.togglePlaylist()'); self.oPlaylist.toggleDisplay(); self.oSMPlayer.togglePlaylist(); } this.init = function() { self.oSMPlayer = new SMPlayer(self); } } var soundPlayer = new SoundPlayer(); function initStuff() { soundPlayer.init(); // load mp3, etc. setTimeout(initOtherStuff,20); } function initOtherStuff() { soundPlayer.oPlaylist = new SPPlaylist(soundPlayer,null); soundPlayer.oPlaylist.searchForSoundLinks(); soundPlayer.oPlaylist.createPlaylist(); soundPlayer.oPlaylist.createTweens(); // make tweens for playlist soundPlayer.oPlaylist.loadFromHash(); } soundManager.debugMode = (window.location.toString().match(/debug=1/)?true:false); // set debug mode soundManager.defaultOptions.multiShot = false; soundManager.onload = function() { // called after window.onload() + SoundManager is loaded soundManager._writeDebug('www.schillmania.com/projects/soundmanager2/'); soundManager._writeDebug('-- jsAMP v0.99a.20080331 --',1); initStuff(); }