/* SoundManager 2: Javascript Sound for the Web. -------------------------------------------------- http://www.schillmania.com/projects/soundmanager2/ Copyright (c) 2007, Scott Schiller. All rights reserved. Code licensed under the BSD License: http://www.schillmania.com/projects/soundmanager2/license.txt Beta V2.0b.20070118 */ function SoundManager(smURL,smID) { var self = this; this.enabled = false; this.o = null; this.url = (smURL||s5Path+'ui/audio_support/soundmanager2.swf'); this.id = (smID||'sm2movie'); this.oMC = null; this.sounds = []; this.soundIDs = []; this.allowPolling = true; // allow flash to poll for status update (required for "while playing", "progress" etc. to work.) this.isIE = (navigator.userAgent.match(/MSIE/)); this.isSafari = (navigator.userAgent.match(/safari/i)); this._didAppend = false; this._didInit = false; this._disabled = false; this.version = 'V2.0b.20070118'; this.defaultOptions = { // ----------------- 'debugMode': false, // enable debug/status messaging, handy for development/troubleshooting - will be written to a DIV with an ID of "soundmanager-debug", you can use CSS to make it pretty 'autoLoad': false, // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can) 'stream': true, // allows playing before entire file has loaded (recommended) 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true) 'onid3': null, // callback function for "ID3 data is added/available" 'onload': null, // callback function for "load finished" 'whileloading': null, // callback function for "download progress update" (X of Y bytes received) 'onplay': null, // callback for "play" start 'whileplaying': null, // callback during play (position update) 'onstop': null, // callback for "user stop" 'onfinish': null, // callback function for "sound finished playing" 'onbeforefinish': null, // callback for "before sound finished playing (at [time])" 'onbeforefinishtime': 2000, // offset (milliseconds) before end of sound to trigger beforefinish (eg. 1000 msec = 1 second) 'onbeforefinishcomplete':null, // function to call when said sound finishes playing 'onjustbeforefinish':null, // callback for [n] msec before end of current sound 'onjustbeforefinishtime':200, // [n] - if not using, set to 0 (or null handler) and event will not fire. 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time 'pan': 0, // "pan" settings, left-to-right, -100 to 100 'volume': 100, // self-explanatory. 0-100, the latter being the max. // ----------------- 'foo': 'bar' // you don't need to change this one - it's actually an intentional filler. } // --- public methods --- this.getMovie = function(smID) { // return self.isIE?window[smID]:document[smID]; return self.isIE?window[smID]:(self.isSafari?document[smID+'-embed']:document.getElementById(smID+'-embed')); } this.loadFromXML = function(sXmlUrl) { try { self.o._loadFromXML(sXmlUrl); } catch(e) { self._failSafely(); return true; } } this.createSound = function(oOptions) { if (!self._didInit) throw new Error('soundManager.createSound(): Not loaded yet - wait for soundManager.onload() before calling sound-related methods'); if (arguments.length==2) { // function overloading in JS! :) ..assume simple createSound(id,url) use case oOptions = {'id':arguments[0],'url':arguments[1]} } var thisOptions = self._mergeObjects(oOptions); self._writeDebug('soundManager.createSound(): "'+thisOptions.id+'" ('+thisOptions.url+')'); if (self._idCheck(thisOptions.id,true)) { self._writeDebug('sound '+thisOptions.id+' already defined - exiting'); return false; } self.sounds[thisOptions.id] = new SMSound(self,thisOptions); self.soundIDs[self.soundIDs.length] = thisOptions.id; try { self.o._createSound(thisOptions.id,thisOptions.onjustbeforefinishtime); } catch(e) { self._failSafely(); return true; } if (thisOptions.autoLoad || thisOptions.autoPlay) self.sounds[thisOptions.id].load(thisOptions); if (thisOptions.autoPlay) self.sounds[thisOptions.id].playState = 1; // we can only assume this sound will be playing soon. } this.load = function(sID,oOptions) { if (!self._idCheck(sID)) return false; self.sounds[sID].load(oOptions); } this.unload = function(sID) { if (!self._idCheck(sID)) return false; self.sounds[sID].unload(); } this.play = function(sID,oOptions) { if (!self._idCheck(sID)) { if (typeof oOptions != 'Object') oOptions = {url:oOptions}; // overloading use case: play('mySound','/path/to/some.mp3'); if (oOptions && oOptions.url) { // overloading use case, creation + playing of sound: .play('someID',{url:'/path/to.mp3'}); self._writeDebug('soundController.play(): attempting to create "'+sID+'"'); oOptions.id = sID; self.createSound(oOptions); } else { return false; } } self.sounds[sID].play(oOptions); } this.start = this.play; // just for convenience this.setPosition = function(sID,nMsecOffset) { if (!self._idCheck(sID)) return false; self.sounds[sID].setPosition(nMsecOffset); } this.stop = function(sID) { if (!self._idCheck(sID)) return false; self._writeDebug('soundManager.stop('+sID+')'); self.sounds[sID].stop(); } this.stopAll = function() { for (var oSound in self.sounds) { if (oSound instanceof SMSound) oSound.stop(); // apply only to sound objects } } this.pause = function(sID) { if (!self._idCheck(sID)) return false; self.sounds[sID].pause(); } this.resume = function(sID) { if (!self._idCheck(sID)) return false; self.sounds[sID].resume(); } this.togglePause = function(sID) { if (!self._idCheck(sID)) return false; self.sounds[sID].togglePause(); } this.setPan = function(sID,nPan) { if (!self._idCheck(sID)) return false; self.sounds[sID].setPan(nPan); } this.setVolume = function(sID,nVol) { if (!self._idCheck(sID)) return false; self.sounds[sID].setVolume(nVol); } this.setPolling = function(bPolling) { if (!self.o || !self.allowPolling) return false; self._writeDebug('soundManager.setPolling('+bPolling+')'); self.o._setPolling(bPolling); } this.disable = function() { // destroy all functions if (self._disabled) return false; self._disabled = true; self._writeDebug('soundManager.disable(): Disabling all functions - future calls will return false.'); for (var i=self.soundIDs.length; i--;) { self._disableObject(self.sounds[self.soundIDs[i]]); } self.initComplete(); // fire "complete", despite fail self._disableObject(self); } this.getSoundById = function(sID,suppressDebug) { if (!sID) throw new Error('SoundManager.getSoundById(): sID is null/undefined'); var result = self.sounds[sID]; if (!result && !suppressDebug) { self._writeDebug('"'+sID+'" is an invalid sound ID.'); // soundManager._writeDebug('trace: '+arguments.callee.caller); } return result; } this.onload = function() { onloadSM2(); // window.onload() equivalent for SM2, ready to create sounds etc. // this is a stub - you can override this in your own external script, eg. soundManager.onload = function() {} // soundManager._writeDebug('Warning: soundManager.onload() is undefined.'); } this.onerror = function() { onerrorSM2(); // stub for user handler, called when SM2 fails to load/init } // --- "private" methods --- this._idCheck = this.getSoundById; this._disableObject = function(o) { for (var oProp in o) { if (typeof o[oProp] == 'function') o[oProp] = function(){return false;} } oProp = null; } this._failSafely = function() { // exception handler for "object doesn't support this property or method" var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html'; var fpgssTitle = 'You may need to whitelist this location/domain eg. file://C:/ or C:/ or mysite.com, or set ALWAYS ALLOW under the Flash Player Global Security Settings page. Note that this seems to apply only to file system viewing.'; var flashCPL = 'view/edit'; var FPGSS = 'FPGSS'; if (!self._disabled) { self._writeDebug('soundManager: JS->Flash communication failed. Possible causes: flash/browser security restrictions ('+flashCPL+'), insufficient browser/plugin support, or .swf not found'); self._writeDebug('Verify that the movie path of '+self.url+' is correct (test link)'); if (self._didAppend) { if (!document.domain) { self._writeDebug('Loading from local file system? (document.domain appears to be null, this location may need whitelisting by '+FPGSS+')'); self._writeDebug('Possible security/domain restrictions ('+flashCPL+'), should work when served by http on same domain'); } // self._writeDebug('Note: Movie added via JS method, static object/embed in-page markup method may work instead.'); } self.disable(); } } this._createMovie = function(smID,smURL) { var useDOM = !self.isIE; // IE needs document.write() to work, long story short - lame if (window.location.href.indexOf('debug=1')+1) self.defaultOptions.debugMode = true; // allow force of debug mode via URL var html = ['','']; var debugID = 'soundmanager-debug'; var debugHTML = '
'; if (useDOM) { self.oMC = document.createElement('div'); self.oMC.className = 'movieContainer'; // "hide" flash movie self.oMC.style.position = 'absolute'; self.oMC.style.left = '-256px'; self.oMC.style.width = '1px'; self.oMC.style.height = '1px'; self.oMC.innerHTML = html[self.isIE?0:1]; var oTarget = (!self.isIE && document.body?document.body:document.getElementsByTagName('div')[0]); oTarget.appendChild(self.oMC); // append to body here can throw "operation aborted" under IE if (!document.getElementById(debugID)) { var oDebug = document.createElement('div'); oDebug.id = debugID; oDebug.style.display = (self.defaultOptions.debugMode?'block':'none'); oTarget.appendChild(oDebug); } oTarget = null; } else { // IE method - local file system, may cause strange JS error at line 53? // I hate this method, but it appears to be the only one that will work (createElement works, but JS->Flash communication oddly fails when viewing locally.) // Finally, IE doesn't do application/xhtml+xml yet (thus document.write() is still minimally acceptable here.) if (document.getElementById(debugID)) debugHTML = ''; // avoid overwriting document.write('