/*! SoundManager 2: Javascript Sound for the Web -------------------------------------------- http://schillmania.com/projects/soundmanager2/ Copyright (c) 2008, Scott Schiller. All rights reserved. Code licensed under the BSD License: http://schillmania.com/projects/soundmanager2/license.txt V2.90a.20081028 */ function SoundManager(smURL,smID) { this.flashVersion = 8; // version of flash to require, either 8 or 9. Some API features require Flash 9. this.debugMode = true; // enable debugging output (div#soundmanager-debug, OR console if available + configured) this.useConsole = true; // use firebug/safari console.log()-type debug console if available this.consoleOnly = false; // if console is being used, do not create/write to #soundmanager-debug this.waitForWindowLoad = false; // force SM2 to wait for window.onload() before trying to call soundManager.onload() this.nullURL = 'null.mp3'; // path to "null" (empty) MP3 file, used to unload sounds (Flash 8 only) this.allowPolling = true; // allow flash to poll for status update (required for "while playing", peak, sound spectrum functions to work.) this.useMovieStar = false; // enable support for Flash 9.0r115+ (codename "MovieStar") MPEG4 audio + video formats (AAC, M4V, FLV, MOV etc.) this.useHighPerformance = true; // flash positioning trick, improves JS/flash callback speed, minimizes delay this.bgColor = '#ffffff'; // movie (.swf) background color, useful if showing on-screen for video etc. this.defaultOptions = { '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 'onpause': null, // callback for "pause" 'onresume': null, // callback for "resume" (pause toggle) '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': 5000, // 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 'position': null, // offset (milliseconds) to seek to within loaded sound data. 'pan': 0, // "pan" settings, left-to-right, -100 to 100 'volume': 100 // self-explanatory. 0-100, the latter being the max. }; this.flash9Options = { // flash 9-only options, merged into defaultOptions if flash 9 is being used 'isMovieStar': null, // "MovieStar" MPEG4 audio/video mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL 'usePeakData': false, // enable left/right channel peak (level) data 'useWaveformData': false, // enable sound spectrum (raw waveform data) - WARNING: CPU-INTENSIVE: may set CPUs on fire. 'useEQData': false // enable sound EQ (frequency spectrum data) - WARNING: Also CPU-intensive. }; this.movieStarOptions = { // flash 9.0r115+ MPEG4 audio/video options, merged into defaultOptions if flash 9 + movieStar mode is enabled 'onmetadata': null, // callback for when video width/height etc. are received 'useVideo': false // if loading movieStar content, whether to show video } this.flashBlockHelper = { 'enabled': false, // experimental, removed with >v2.80 'message': [] // "nag bar" to show when messaging the user, if SM2 fails on firefox etc. }; var _s = this; this.version = null; this.versionNumber = 'V2.90a.20081028'; this.movieURL = null; this.url = null; this.altURL = null; this.swfLoaded = false; this.enabled = false; this.o = null; this.id = (smID||'sm2movie'); this.oMC = null; this.sounds = []; this.soundIDs = []; this.muted = false; this.isIE = (navigator.userAgent.match(/MSIE/i)); this.isSafari = (navigator.userAgent.match(/safari/i)); this.isGecko = (navigator.userAgent.match(/gecko/i)); this.debugID = 'soundmanager-debug'; this._debugOpen = true; this._didAppend = false; this._appendSuccess = false; this._didInit = false; this._disabled = false; this._windowLoaded = false; this._hasConsole = (typeof console != 'undefined' && typeof console.log != 'undefined'); this._debugLevels = ['log','info','warn','error']; this._defaultFlashVersion = 8; this.filePatterns = { flash8: /\.(mp3)/i, flash9: /\.(mp3)/i }; this.netStreamTypes = ['aac','flv','mov','mp4','m4v','f4v','m4a','mp4v','3gp','3g2']; // Flash v9.0r115+ "moviestar" formats this.netStreamPattern = new RegExp('.('+this.netStreamTypes.join('|')+')','i'); this.filePattern = null; this.features = { peakData: false, waveformData: false, eqData: false }; this.sandbox = { 'type': null, 'types': { 'remote': 'remote (domain-based) rules', 'localWithFile': 'local with file access (no internet access)', 'localWithNetwork': 'local with network (internet access only, no local access)', 'localTrusted': 'local, trusted (local + internet access)' }, 'description': null, 'noRemote': null, 'noLocal': null }; this._setVersionInfo = function() { if (_s.flashVersion != 8 && _s.flashVersion != 9) { alert('soundManager.flashVersion must be 8 or 9. "'+_s.flashVersion+'" is invalid. Reverting to '+_s._defaultFlashVersion+'.'); _s.flashVersion = _s._defaultFlashVersion; } _s.version = _s.versionNumber+(_s.flashVersion==9?' (AS3/Flash 9)':' (AS2/Flash 8)'); // set up default options if (_s.flashVersion > 8) { _s.defaultOptions = _s._mergeObjects(_s.defaultOptions,_s.flash9Options); } if (_s.flashVersion > 8 && _s.useMovieStar) { _s.defaultOptions = _s._mergeObjects(_s.defaultOptions,_s.movieStarOptions); _s.filePatterns.flash9 = new RegExp('.(mp3|'+_s.netStreamTypes.join('|')+')','i'); } else { _s.useMovieStar = false; } _s.filePattern = _s.filePatterns[(_s.flashVersion!=8?'flash9':'flash8')]; _s.movieURL = (_s.flashVersion==8?'soundmanager2.swf':'soundmanager2_flash9.swf'); _s.features.peakData = _s.features.waveformData = _s.features.eqData = (_s.flashVersion==9); } this._overHTTP = (document.location?document.location.protocol.match(/http/i):null); this._waitingforEI = false; this._initPending = false; this._tryInitOnFocus = (this.isSafari && typeof document.hasFocus == 'undefined'); this._isFocused = (typeof document.hasFocus != 'undefined'?document.hasFocus():null); this._okToDisable = !this._tryInitOnFocus; this.useAltURL = !this._overHTTP; // use altURL if not "online" var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html'; // --- public methods --- this.supported = function() { return (_s._didInit && !_s._disabled); }; this.getMovie = function(smID) { return _s.isIE?window[smID]:(_s.isSafari?document.getElementById(smID)||document[smID]:document.getElementById(smID)); }; this.loadFromXML = function(sXmlUrl) { try { _s.o._loadFromXML(sXmlUrl); } catch(e) { _s._failSafely(); return true; }; }; this.createSound = function(oOptions) { if (!_s._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 var oOptions = {'id':arguments[0],'url':arguments[1]}; }; var thisOptions = _s._mergeObjects(oOptions); // inherit SM2 defaults var _tO = thisOptions; // alias _s._wD('soundManager.createSound(): '+_tO.id+' ('+_tO.url+')',1); if (_s._idCheck(_tO.id,true)) { _s._wD('soundManager.createSound(): '+_tO.id+' exists',1); return _s.sounds[_tO.id]; }; if (_s.flashVersion > 8 && _s.useMovieStar) { if (_tO.isMovieStar == null) { _tO.isMovieStar = (_tO.url.match(_s.netStreamPattern)?true:false); } if (_tO.isMovieStar) { _s._wD('soundManager.createSound(): using MovieStar handling'); } if (_tO.isMovieStar && (_tO.usePeakData || _tO.useWaveformData || _tO.useEQData)) { _s._wD('Warning: peak/waveform/eqData features unsupported for non-MP3 formats'); _tO.usePeakData = false; _tO.useWaveformData = false; _tO.useEQData = false; } }; _s.sounds[_tO.id] = new SMSound(_tO); _s.soundIDs[_s.soundIDs.length] = _tO.id; // AS2: if (_s.flashVersion == 8) { _s.o._createSound(_tO.id,_tO.onjustbeforefinishtime); } else { _s.o._createSound(_tO.id,_tO.url,_tO.onjustbeforefinishtime,_tO.usePeakData,_tO.useWaveformData,_tO.useEQData,_tO.isMovieStar,(_tO.isMovieStar?_tO.useVideo:false)); }; if (_tO.autoLoad || _tO.autoPlay) { window.setTimeout(function() { if (_s.sounds[_tO.id]) { _s.sounds[_tO.id].load(_tO); } },20); } if (_tO.autoPlay) { if (_s.flashVersion == 8) { _s.sounds[_tO.id].playState = 1; // we can only assume this sound will be playing soon. } else { _s.sounds[_tO.id].play(); } } return _s.sounds[_tO.id]; }; this.createVideo = function(oOptions) { if (arguments.length==2) { var oOptions = {'id':arguments[0],'url':arguments[1]}; }; if (_s.flashVersion >= 9) { oOptions.isMovieStar = true; oOptions.useVideo = true; } else { _s._wD('soundManager.createVideo(): flash 9 required for video. Exiting.',2); return false; } if (!_s.useMovieStar) { _s._wD('soundManager.createVideo(): MovieStar mode not enabled. Exiting.',2); } return _s.createSound(oOptions); } this.destroySound = function(sID,bFromSound) { // explicitly destroy a sound before normal page unload, etc. if (!_s._idCheck(sID)) return false; for (var i=0; i<_s.soundIDs.length; i++) { if (_s.soundIDs[i] == sID) { _s.soundIDs.splice(i,1); continue; }; }; // conservative option: avoid crash with ze flash 8 // calling destroySound() within a sound onload() might crash firefox, certain flavours of winXP + flash 8?? // if (_s.flashVersion != 8) { _s.sounds[sID].unload(); // } if (!bFromSound) { // ignore if being called from SMSound instance _s.sounds[sID].destruct(); }; delete _s.sounds[sID]; }; this.destroyVideo = this.destroySound; this.load = function(sID,oOptions) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].load(oOptions); }; this.unload = function(sID) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].unload(); }; this.play = function(sID,oOptions) { if (!_s._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'}); _s._wD('soundController.play(): attempting to create "'+sID+'"',1); oOptions.id = sID; _s.createSound(oOptions); } else { return false; }; }; _s.sounds[sID].play(oOptions); }; this.start = this.play; // just for convenience this.setPosition = function(sID,nMsecOffset) { if (!_s._idCheck(sID)) return false; nMsecOffset = Math.min((nMsecOffset||0),_s.duration); // don't allow seek past loaded duration _s.sounds[sID].setPosition(nMsecOffset); }; this.stop = function(sID) { if (!_s._idCheck(sID)) return false; _s._wD('soundManager.stop('+sID+')',1); _s.sounds[sID].stop(); }; this.stopAll = function() { _s._wD('soundManager.stopAll()',1); for (var oSound in _s.sounds) { if (_s.sounds[oSound] instanceof SMSound) _s.sounds[oSound].stop(); // apply only to sound objects }; }; this.pause = function(sID) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].pause(); }; this.pauseAll = function() { for (var i=_s.soundIDs.length; i--;) { _s.sounds[_s.soundIDs[i]].pause(); } }; this.resume = function(sID) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].resume(); }; this.resumeAll = function() { for (var i=_s.soundIDs.length; i--;) { _s.sounds[_s.soundIDs[i]].resume(); } }; this.togglePause = function(sID) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].togglePause(); }; this.setPan = function(sID,nPan) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].setPan(nPan); }; this.setVolume = function(sID,nVol) { if (!_s._idCheck(sID)) return false; _s.sounds[sID].setVolume(nVol); }; this.mute = function(sID) { if (typeof sID != 'string') sID = null; if (!sID) { var o = null; _s._wD('soundManager.mute(): Muting all sounds'); for (var i=_s.soundIDs.length; i--;) { _s.sounds[_s.soundIDs[i]].mute(); } _s.muted = true; } else { if (!_s._idCheck(sID)) return false; _s._wD('soundManager.mute(): Muting "'+sID+'"'); _s.sounds[sID].mute(); } }; this.muteAll = function() { _s.mute(); }; this.unmute = function(sID) { if (typeof sID != 'string') sID = null; if (!sID) { var o = null; _s._wD('soundManager.unmute(): Unmuting all sounds'); for (var i=_s.soundIDs.length; i--;) { _s.sounds[_s.soundIDs[i]].unmute(); } _s.muted = false; } else { if (!_s._idCheck(sID)) return false; _s._wD('soundManager.unmute(): Unmuting "'+sID+'"'); _s.sounds[sID].unmute(); } }; this.unmuteAll = function() { _s.unmute(); }; this.setPolling = function(bPolling) { if (!_s.o || !_s.allowPolling) return false; // _s._wD('soundManager.setPolling('+bPolling+')'); _s.o._setPolling(bPolling); }; this.disable = function(bUnload) { // destroy all functions if (_s._disabled) return false; _s._disabled = true; _s._wD('soundManager.disable(): Disabling all functions - future calls will return false.',1); for (var i=_s.soundIDs.length; i--;) { _s._disableObject(_s.sounds[_s.soundIDs[i]]); }; _s.initComplete(); // fire "complete", despite fail _s._disableObject(_s); }; this.handleFlashBlock = function(bForce) { // experimental, removed with >v2.80. return false; }; this.canPlayURL = function(sURL) { return (sURL?(sURL.match(_s.filePattern)?true:false):null); }; this.getSoundById = function(sID,suppressDebug) { if (!sID) throw new Error('SoundManager.getSoundById(): sID is null/undefined'); var result = _s.sounds[sID]; if (!result && !suppressDebug) { _s._wD('"'+sID+'" is an invalid sound ID.',2); // soundManager._wD('trace: '+arguments.callee.caller); }; return result; }; this.onload = function() { // 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._wD('Warning: soundManager.onload() is undefined.',2); }; this.onerror = function() { // 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' && typeof o[oProp]._protected == 'undefined') o[oProp] = function(){return false;}; }; oProp = null; }; this._failSafely = function() { // exception handler for "object doesn't support this property or method" or general failure 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. The latter is probably less-secure.'; var flashCPL = 'view/edit'; var FPGSS = 'FPGSS'; if (!_s._disabled) { _s._wD('soundManager: Failed to initialise.',2); _s.disable(); }; }; this._normalizeMovieURL = function(smURL) { if (smURL) { if (smURL.match(/\.swf/)) { smURL = smURL.substr(0,smURL.lastIndexOf('.swf')); } if (smURL.lastIndexOf('/') != smURL.length-1) { smURL = smURL+'/'; } } return(smURL && smURL.lastIndexOf('/')!=-1?smURL.substr(0,smURL.lastIndexOf('/')+1):'./')+_s.movieURL; }; this._getDocument = function() { return (document.body?document.body:(document.documentElement?document.documentElement:document.getElementsByTagName('div')[0])); }; this._getDocument._protected = true; this._createMovie = function(smID,smURL) { if (_s._didAppend && _s._appendSuccess) return false; // ignore if already succeeded if (window.location.href.indexOf('debug=1')+1) _s.debugMode = true; // allow force of debug mode via URL _s._didAppend = true; // safety check for legacy (change to Flash 9 URL) _s._setVersionInfo(); var remoteURL = (smURL?smURL:_s.url); var localURL = (_s.altURL?_s.altURL:remoteURL); _s.url = _s._normalizeMovieURL(_s._overHTTP?remoteURL:localURL); smURL = _s.url; var htmlEmbed = ''; var htmlObject = ''+(_s.useHighPerformance && !_s.useMovieStar?' ':'')+''; var html = (!_s.isIE?htmlEmbed:htmlObject); var toggleElement = '
-
'; var debugHTML = '
'; var appXHTML = 'soundManager._createMovie(): appendChild/innerHTML set failed. May be app/xhtml+xml DOM-related.'; var oTarget = _s._getDocument(); if (oTarget) { _s.oMC = document.getElementById('sm2-container')?document.getElementById('sm2-container'):document.createElement('div'); if (!_s.oMC.id) { _s.oMC.id = 'sm2-container'; _s.oMC.className = 'movieContainer'; // "hide" flash movie var s = null; if (_s.useHighPerformance) { s = { position: 'fixed', width: '8px', height: '8px', // must be at least 6px for flash to run fast. odd? yes. bottom: '0px', left: '0px', zIndex:-1 // sit behind everything else } } else { s = { position: 'absolute', width: '1px', height: '1px', bottom: '0px', left: '0px' } } var x = null; for (x in s) { _s.oMC.style[x] = s[x]; } try { oTarget.appendChild(_s.oMC); _s.oMC.innerHTML = html; _s._appendSuccess = true; } catch(e) { throw new Error(appXHTML); } } else { // it's already in the document. _s.oMC.innerHTML = html; _s._appendSuccess = true; } if (!document.getElementById(_s.debugID) && ((!_s._hasConsole||!_s.useConsole)||(_s.useConsole && _s._hasConsole && !_s.consoleOnly))) { var oDebug = document.createElement('div'); oDebug.id = _s.debugID; oDebug.style.display = (_s.debugMode?'block':'none'); if (_s.debugMode) { try { var oD = document.createElement('div'); oTarget.appendChild(oD); oD.innerHTML = toggleElement; } catch(e) { throw new Error(appXHTML); }; }; oTarget.appendChild(oDebug); }; oTarget = null; }; _s._wD('-- SoundManager 2 '+_s.version+(_s.useMovieStar?', MovieStar mode':'')+(_s._wD?', high performance mode':'')+' --',1); _s._wD('soundManager._createMovie(): Trying to load '+smURL+(!_s._overHTTP && _s.altURL?'(alternate URL)':''),1); }; // aliased to this._wD() this._writeDebug = function(sText,sType,bTimestamp) { if (!_s.debugMode) return false; if (typeof bTimestamp != 'undefined' && bTimestamp) { sText = sText + ' | '+new Date().getTime(); }; if (_s._hasConsole && _s.useConsole) { var sMethod = _s._debugLevels[sType]; if (typeof console[sMethod] != 'undefined') { console[sMethod](sText); } else { console.log(sText); }; if (_s.useConsoleOnly) return true; }; var sDID = 'soundmanager-debug'; try { var o = document.getElementById(sDID); if (!o) return false; var oItem = document.createElement('div'); sText = sText.replace(/\n/g,'
'); if (typeof sType == 'undefined') { var sType = 0; } else { sType = parseInt(sType); }; oItem.innerHTML = sText; if (sType) { if (sType >= 2) oItem.style.fontWeight = 'bold'; if (sType == 3) oItem.style.color = '#ff3333'; }; // o.appendChild(oItem); // top-to-bottom o.insertBefore(oItem,o.firstChild); // bottom-to-top } catch(e) { // oh well }; o = null; }; this._writeDebug._protected = true; this._wD = this._writeDebug; this._wDAlert = function(sText) { alert(sText); }; if (window.location.href.indexOf('debug=alert')+1 && _s.debugMode) { _s._wD = _s._wDAlert; }; this._toggleDebug = function() { var o = document.getElementById(_s.debugID); var oT = document.getElementById(_s.debugID+'-toggle'); if (!o) return false; if (_s._debugOpen) { // minimize oT.innerHTML = '+'; o.style.display = 'none'; } else { oT.innerHTML = '-'; o.style.display = 'block'; }; _s._debugOpen = !_s._debugOpen; }; this._toggleDebug._protected = true; this._debug = function() { _s._wD('--- soundManager._debug(): Current sound objects ---',1); for (var i=0,j=_s.soundIDs.length; i Flash either. soundManager.onerror(); soundManager.disable(); }; if (document.addEventListener) document.addEventListener('DOMContentLoaded',_s.domContentLoaded,false); }; // SoundManager() var soundManager = new SoundManager();