1/* 2 SoundManager 2: Javascript Sound for the Web 3 -------------------------------------------- 4 http://www.schillmania.com/projects/soundmanager2/ 5 6 Copyright (c) 2007, Scott Schiller. All rights reserved. 7 Code licensed under the BSD License: 8 http://www.schillmania.com/projects/soundmanager2/license.txt 9 10 V2.0b.20070415 11*/ 12 13function SoundManager(smURL,smID) { 14 var self = this; 15 this.version = 'V2.0b.20070415'; 16 this.url = (smURL||'soundmanager2.swf'); 17 18 this.debugMode = true; // enable debugging output (div#soundmanager-debug, OR console if available + configured) 19 this.useConsole = true; // use firebug/safari console.log()-type debug console if available 20 this.consoleOnly = false; // if console is being used, do not create/write to #soundmanager-debug 21 this.nullURL = 'data/null.mp3'; // path to "null" (empty) MP3 file, used to unload sounds 22 23 this.defaultOptions = { 24 '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) 25 'stream': true, // allows playing before entire file has loaded (recommended) 26 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true) 27 'onid3': null, // callback function for "ID3 data is added/available" 28 'onload': null, // callback function for "load finished" 29 'whileloading': null, // callback function for "download progress update" (X of Y bytes received) 30 'onplay': null, // callback for "play" start 31 'whileplaying': null, // callback during play (position update) 32 'onstop': null, // callback for "user stop" 33 'onfinish': null, // callback function for "sound finished playing" 34 'onbeforefinish': null, // callback for "before sound finished playing (at [time])" 35 'onbeforefinishtime': 5000, // offset (milliseconds) before end of sound to trigger beforefinish (eg. 1000 msec = 1 second) 36 'onbeforefinishcomplete':null, // function to call when said sound finishes playing 37 'onjustbeforefinish':null, // callback for [n] msec before end of current sound 38 'onjustbeforefinishtime':200, // [n] - if not using, set to 0 (or null handler) and event will not fire. 39 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time 40 'pan': 0, // "pan" settings, left-to-right, -100 to 100 41 'volume': 100 // self-explanatory. 0-100, the latter being the max. 42 } 43 44 this.allowPolling = true; // allow flash to poll for status update (required for "while playing", "progress" etc. to work.) 45 this.enabled = false; 46 this.o = null; 47 this.id = (smID||'sm2movie'); 48 this.oMC = null; 49 this.sounds = []; 50 this.soundIDs = []; 51 this.isIE = (navigator.userAgent.match(/MSIE/)); 52 this.isSafari = (navigator.userAgent.match(/safari/i)); 53 this.debugID = 'soundmanager-debug'; 54 this._debugOpen = true; 55 this._didAppend = false; 56 this._appendSuccess = false; 57 this._didInit = false; 58 this._disabled = false; 59 this._hasConsole = (typeof console != 'undefined' && typeof console.log != 'undefined'); 60 this._debugLevels = !self.isSafari?['debug','info','warn','error']:['log','log','log','log']; 61 62 // --- public methods --- 63 64 this.getMovie = function(smID) { 65 // return self.isIE?window[smID]:document[smID]; 66 return self.isIE?window[smID]:(self.isSafari?document[smID+'-embed']:document.getElementById(smID+'-embed')); 67 } 68 69 this.loadFromXML = function(sXmlUrl) { 70 try { 71 self.o._loadFromXML(sXmlUrl); 72 } catch(e) { 73 self._failSafely(); 74 return true; 75 } 76 } 77 78 this.createSound = function(oOptions) { 79 if (!self._didInit) throw new Error('soundManager.createSound(): Not loaded yet - wait for soundManager.onload() before calling sound-related methods'); 80 if (arguments.length==2) { 81 // function overloading in JS! :) ..assume simple createSound(id,url) use case 82 oOptions = {'id':arguments[0],'url':arguments[1]} 83 } 84 var thisOptions = self._mergeObjects(oOptions); 85 self._writeDebug('soundManager.createSound(): "<a href="#" onclick="soundManager.play(\''+thisOptions.id+'\');return false" title="play this sound">'+thisOptions.id+'</a>" ('+thisOptions.url+')',1); 86 if (self._idCheck(thisOptions.id,true)) { 87 self._writeDebug('sound '+thisOptions.id+' already defined - exiting',2); 88 return false;
89 } 90 self.sounds[thisOptions.id] = new SMSound(self,thisOptions); 91 self.soundIDs[self.soundIDs.length] = thisOptions.id; 92 try { 93 self.o._createSound(thisOptions.id,thisOptions.onjustbeforefinishtime); 94 } catch(e) { 95 self._failSafely(); 96 return true; 97 } 98 if (thisOptions.autoLoad || thisOptions.autoPlay) self.sounds[thisOptions.id].load(thisOptions); 99 if (thisOptions.autoPlay) self.sounds[thisOptions.id].playState = 1; // we can only assume this sound will be playing soon. 100 } 101 102 this.destroySound = function(sID) { 103 // explicitly destroy a sound before normal page unload, etc. 104 if (!self._idCheck(sID)) return false; 105 for (var i=self.soundIDs.length; i--;) { 106 if (self.soundIDs[i] == sID) { 107 delete self.soundIDs[i]; 108 continue; 109 } 110 } 111 self.sounds[sID].unload(); 112 delete self.sounds[sID]; 113 } 114 115 this.load = function(sID,oOptions) { 116 if (!self._idCheck(sID)) return false; 117 self.sounds[sID].load(oOptions); 118 } 119 120 this.unload = function(sID) { 121 if (!self._idCheck(sID)) return false; 122 self.sounds[sID].unload(); 123 } 124 125 this.play = function(sID,oOptions) { 126 if (!self._idCheck(sID)) { 127 if (typeof oOptions != 'Object') oOptions = {url:oOptions}; // overloading use case: play('mySound','/path/to/some.mp3'); 128 if (oOptions && oOptions.url) { 129 // overloading use case, creation + playing of sound: .play('someID',{url:'/path/to.mp3'}); 130 self._writeDebug('soundController.play(): attempting to create "'+sID+'"',1); 131 oOptions.id = sID; 132 self.createSound(oOptions); 133 } else { 134 return false; 135 } 136 } 137 self.sounds[sID].play(oOptions); 138 } 139 140 this.start = this.play; // just for convenience 141 142 this.setPosition = function(sID,nMsecOffset) { 143 if (!self._idCheck(sID)) return false; 144 self.sounds[sID].setPosition(nMsecOffset); 145 } 146 147 this.stop = function(sID) { 148 if (!self._idCheck(sID)) return false; 149 self._writeDebug('soundManager.stop('+sID+')',1); 150 self.sounds[sID].stop(); 151 } 152 153 this.stopAll = function() { 154 self._writeDebug('soundManager.stopAll()',1); 155 for (var oSound in self.sounds) { 156 if (self.sounds[oSound] instanceof SMSound) self.sounds[oSound].stop(); // apply only to sound objects 157 } 158 } 159 160 this.pause = function(sID) { 161 if (!self._idCheck(sID)) return false; 162 self.sounds[sID].pause(); 163 } 164 165 this.resume = function(sID) { 166 if (!self._idCheck(sID)) return false; 167 self.sounds[sID].resume(); 168 } 169 170 this.togglePause = function(sID) { 171 if (!self._idCheck(sID)) return false; 172 self.sounds[sID].togglePause(); 173 } 174 175 this.setPan = function(sID,nPan) { 176 if (!self._idCheck(sID)) return false; 177 self.sounds[sID].setPan(nPan); 178 } 179 180 this.setVolume = function(sID,nVol) { 181 if (!self._idCheck(sID)) return false; 182 self.sounds[sID].setVolume(nVol); 183 } 184 185 this.setPolling = function(bPolling) { 186 if (!self.o || !self.allowPolling) return false; 187 self._writeDebug('soundManager.setPolling('+bPolling+')'); 188 self.o._setPolling(bPolling); 189 } 190 191 this.disable = function() { 192 // destroy all functions 193 if (self._disabled) return false; 194 self._disabled = true; 195 self._writeDebug('soundManager.disable(): Disabling all functions - future calls will return false.',1); 196 for (var i=self.soundIDs.length; i--;) { 197 self._disableObject(self.sounds[self.soundIDs[i]]); 198 } 199 self.initComplete(); // fire "complete", despite fail 200 self._disableObject(self); 201 } 202 203 this.getSoundById = function(sID,suppressDebug) { 204 if (!sID) throw new Error('SoundManager.getSoundById(): sID is null/undefined'); 205 var result = self.sounds[sID]; 206 if (!result && !suppressDebug) { 207 self._writeDebug('"'+sID+'" is an invalid sound ID.',2); 208 // soundManager._writeDebug('trace: '+arguments.callee.caller); 209 } 210 return result; 211 } 212 213 this.onload = function() { 214 // window.onload() equivalent for SM2, ready to create sounds etc. 215 // this is a stub - you can override this in your own external script, eg. soundManager.onload = function() {} 216 soundManager._writeDebug('<em>Warning</em>: soundManager.onload() is undefined.',2); 217 } 218 219 this.onerror = function() { 220 // stub for user handler, called when SM2 fails to load/init 221 } 222 223 // --- "private" methods --- 224
225 this._idCheck = this.getSoundById; 226 227 this._disableObject = function(o) { 228 for (var oProp in o) { 229 if (typeof o[oProp] == 'function' && typeof o[oProp]._protected == 'undefined') o[oProp] = function(){return false;} 230 } 231 oProp = null; 232 } 233 234 this._failSafely = function() { 235 // exception handler for "object doesn't support this property or method" 236 var flashCPLink = 'http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html'; 237 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.'; 238 var flashCPL = '<a href="'+flashCPLink+'" title="'+fpgssTitle+'">view/edit</a>'; 239 var FPGSS = '<a href="'+flashCPLink+'" title="Flash Player Global Security Settings">FPGSS</a>'; 240 if (!self._disabled) { 241 self._writeDebug('soundManager: JS->Flash communication failed. Possible causes: flash/browser security restrictions ('+flashCPL+'), insufficient browser/plugin support, or .swf not found',2); 242 self._writeDebug('Verify that the movie path of <em>'+self.url+'</em> is correct (<a href="'+self.url+'" title="If you get a 404/not found, fix it!">test link</a>)',1); 243 if (self._didAppend) { 244 if (!document.domain) { 245 self._writeDebug('Loading from local file system? (document.domain appears to be null, this URL path may need to be added to \'trusted locations\' in '+FPGSS+')',1); 246 self._writeDebug('Possible security/domain restrictions ('+flashCPL+'), should work when served by http on same domain',1); 247 } 248 // self._writeDebug('Note: Movie added via JS method, static object/embed in-page markup method may work instead.'); 249 } 250 self.disable(); 251 } 252 } 253 254 this._createMovie = function(smID,smURL) { 255 if (self._didAppend && self._appendSuccess) return false; // ignore if already succeeded 256 if (window.location.href.indexOf('debug=1')+1) self.debugMode = true; // allow force of debug mode via URL 257 self._didAppend = true; 258 var html = ['<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" codebase="http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=8,0,0,0" width="16" height="16" id="'+smID+'"><param name="movie" value="'+smURL+'"><param name="quality" value="high"><param name="allowScriptAccess" value="always" /></object>','<embed name="'+smID+'-embed" id="'+smID+'-embed" src="'+smURL+'" width="1" height="1" quality="high" allowScriptAccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"></embed>']; 259 var toggleElement = '<div id="'+self.debugID+'-toggle" style="position:fixed;_position:absolute;right:0px;bottom:0px;_top:0px;width:1.2em;height:1.2em;line-height:1.2em;margin:2px;padding:0px;text-align:center;border:1px solid #999;cursor:pointer;background:#fff;color:#333;z-index:706" title="Toggle SM2 debug console" onclick="soundManager._toggleDebug()">-</div>'; 260 var debugHTML = '<div id="'+self.debugID+'" style="display:'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))?'block':'none')+';opacity:0.85"></div>'; 261 var appXHTML = 'soundManager._createMovie(): appendChild/innerHTML set failed. Serving application/xhtml+xml MIME type? Browser may be enforcing strict rules, not allowing write to innerHTML. (PS: If so, this means your commitment to XML validation is going to break stuff now, because this part isn\'t finished yet. ;))'; 262 263 var sHTML = '<div style="position:absolute;left:-256px;top:-256px;width:1px;height:1px" class="movieContainer">'+html[self.isIE?0:1]+'</div>'+(self.debugMode && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly)) && !document.getElementById(self.debugID)?'x'+debugHTML+toggleElement:''); 264 265 var oTarget = (document.body?document.body:document.getElementsByTagName('div')[0]); 266 if (oTarget) { 267 self.oMC = document.createElement('div'); 268 self.oMC.className = 'movieContainer'; 269 // "hide" flash movie 270 self.oMC.style.position = 'absolute'; 271 self.oMC.style.left = '-256px'; 272 self.oMC.style.width = '1px'; 273 self.oMC.style.height = '1px';
274 try { 275 oTarget.appendChild(self.oMC); 276 self.oMC.innerHTML = html[self.isIE?0:1]; 277 self._appendSuccess = true; 278 } catch(e) { 279 // may fail under app/xhtml+xml - has yet to be tested 280 throw new Error(appXHTML); 281 } 282 if (!document.getElementById(self.debugID) && ((!self._hasConsole||!self.useConsole)||(self.useConsole && self._hasConsole && !self.consoleOnly))) { 283 var oDebug = document.createElement('div'); 284 oDebug.id = self.debugID; 285 oDebug.style.display = (self.debugMode?'block':'none'); 286 if (self.debugMode) { 287 try { 288 var oD = document.createElement('div'); 289 oTarget.appendChild(oD); 290 oD.innerHTML = toggleElement; 291 } catch(e) { 292 throw new Error(appXHTML); 293 } 294 } 295 oTarget.appendChild(oDebug); 296 } 297 oTarget = null; 298 } 299 self._writeDebug('-- SoundManager 2 Version '+self.version.substr(1)+' --',1); 300 self._writeDebug('soundManager._createMovie(): trying to load <a href="'+smURL+'" title="Test this link (404=bad)">'+smURL+'</a>',1); 301 } 302 303 this._writeDebug = function(sText,sType) { 304 if (!self.debugMode) return false; 305 if (self._hasConsole && self.useConsole) { 306 console[self._debugLevels[sType]||'log'](sText); // firebug et al 307 if (self.useConsoleOnly) return true; 308 } 309 var sDID = 'soundmanager-debug'; 310 try { 311 var o = document.getElementById(sDID); 312 if (!o) return false; 313 var p = document.createElement('div'); 314 p.innerHTML = sText; 315 // o.appendChild(p); // top-to-bottom 316 o.insertBefore(p,o.firstChild); // bottom-to-top 317 } catch(e) { 318 // oh well 319 } 320 o = null; 321 } 322 this._writeDebug._protected = true; 323 324 this._writeDebugAlert = function(sText) { alert(sText); } 325 326 if (window.location.href.indexOf('debug=alert')+1) { 327 self.debugMode = true; 328 self._writeDebug = self._writeDebugAlert; 329 } 330 331 this._toggleDebug = function() { 332 var o = document.getElementById(self.debugID); 333 var oT = document.getElementById(self.debugID+'-toggle'); 334 if (!o) return false; 335 if (self._debugOpen) { 336 // minimize 337 oT.innerHTML = '+'; 338 o.style.display = 'none'; 339 } else { 340 oT.innerHTML = '-'; 341 o.style.display = 'block'; 342 } 343 self._debugOpen = !self._debugOpen; 344 } 345 346 this._toggleDebug._protected = true; 347 348 this._debug = function() { 349 self._writeDebug('soundManager._debug(): sounds by id/url:',0); 350 for (var i=0,j=self.soundIDs.length; i<j; i++) { 351 self._writeDebug(self.sounds[self.soundIDs[i]].sID+' | '+self.sounds[self.soundIDs[i]].url,0); 352 } 353 } 354 355 this._mergeObjects = function(oMain,oAdd) { 356 // non-destructive merge 357 var o1 = oMain; 358 var o2 = (typeof oAdd == 'undefined'?self.defaultOptions:oAdd); 359 for (var o in o2) { 360 if (typeof o1[o] == 'undefined') o1[o] = o2[o]; 361 } 362 return o1; 363 } 364 365 this.createMovie = function(sURL) { 366 if (sURL) self.url = sURL; 367 self._initMovie(); 368 } 369 370 this._initMovie = function() { 371 // attempt to get, or create, movie 372 if (self.o) return false; // pre-init may have fired this function before window.onload(), may already exist 373 self.o = self.getMovie(self.id); // try to get flash movie (inline markup) 374 if (!self.o) { 375 // try to create 376 self._createMovie(self.id,self.url); 377 self.o = self.getMovie(self.id); 378 } 379 if (self.o) { 380 self._writeDebug('soundManager._initMovie(): Got '+self.o.nodeName+' element ('+(self._didAppend?'created via JS':'static HTML')+')',1); 381 } 382 } 383 384 this.initComplete = function() { 385 if (self._didInit) return false; 386 self._didInit = true; 387 self._writeDebug('-- SoundManager 2 '+(self._disabled?'failed to load':'loaded')+' ('+(self._disabled?'security/load error':'OK')+') --',1); 388 if (self._disabled) { 389 self._writeDebug('soundManager.initComplete(): calling soundManager.onerror()',1); 390 self.onerror.apply(window); 391 return false; 392 } 393 self._writeDebug('soundManager.initComplete(): calling soundManager.onload()',1); 394 try { 395 // call user-defined "onload", scoped to window 396 self.onload.apply(window); 397 } catch(e) { 398 // something broke (likely JS error in user function) 399 self._writeDebug('soundManager.onload() threw an exception: '+e.message,2); 400 throw e; // (so browser/console gets message) 401 } 402 self._writeDebug('soundManager.onload() complete',1); 403 } 404 405 this.init = function() { 406 // called after onload() 407 // self._initMovie(); 408 // event cleanup 409 if (window.removeEventListener) {
410 window.removeEventListener('load',self.beginInit,false); 411 } else if (window.detachEvent) { 412 window.detachEvent('onload',self.beginInit); 413 } 414 try { 415 self.o._externalInterfaceTest(); // attempt to talk to Flash 416 self._writeDebug('Flash ExternalInterface call (JS -> Flash) succeeded.',1); 417 if (!self.allowPolling) self._writeDebug('Polling (whileloading/whileplaying support) is disabled.',1); 418 self.setPolling(true); 419 self.enabled = true; 420 } catch(e) { 421 self._failSafely(); 422 self.initComplete(); 423 return false; 424 } 425 self.initComplete(); 426 } 427 428 this.beginDelayedInit = function() { 429 setTimeout(self.beginInit,200); 430 } 431 432 this.beginInit = function() { 433 self.createMovie(); // ensure creation if not already done 434 self._initMovie(); 435 setTimeout(self.init,1000); // some delay required, otherwise JS<->Flash/ExternalInterface communication fails under non-IE (?!) 436 } 437 438 this.destruct = function() { 439 if (self.isSafari) { 440 /* -- 441 Safari 1.3.2 (v312.6)/OSX 10.3.9 and perhaps newer will crash if a sound is actively loading when user exits/refreshes/leaves page 442 (Apparently related to ExternalInterface making calls to an unloading/unloaded page?) 443 Unloading sounds (detaching handlers and so on) may help to prevent this 444 -- */ 445 for (var i=self.soundIDs.length; i--;) { 446 if (self.sounds[self.soundIDs[i]].readyState == 1) self.sounds[self.soundIDs[i]].unload(); 447 } 448 } 449 self.disable(); 450 // self.o = null; 451 // self.oMC = null; 452 } 453 454} 455 456function SMSound(oSM,oOptions) { 457 var self = this; 458 var sm = oSM; 459 this.sID = oOptions.id; 460 this.url = oOptions.url; 461 this.options = sm._mergeObjects(oOptions); 462 this.id3 = { 463 /* 464 Name/value pairs set via Flash when available - see reference for names: 465 http://livedocs.macromedia.com/flash/8/main/wwhelp/wwhimpl/common/html/wwhelp.htm?context=LiveDocs_Parts&file=00001567.html 466 (eg., this.id3.songname or this.id3['songname']) 467 */ 468 } 469 470 self.resetProperties = function(bLoaded) { 471 self.bytesLoaded = null; 472 self.bytesTotal = null; 473 self.position = null; 474 self.duration = null; 475 self.durationEstimate = null; 476 self.loaded = false; 477 self.loadSuccess = null; 478 self.playState = 0; 479 self.paused = false; 480 self.readyState = 0; // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success 481 self.didBeforeFinish = false; 482 self.didJustBeforeFinish = false; 483 } 484 485 self.resetProperties(); 486 487 // --- public methods --- 488 489 this.load = function(oOptions) { 490 self.loaded = false; 491 self.loadSuccess = null; 492 self.readyState = 1; 493 self.playState = (oOptions.autoPlay||false); // if autoPlay, assume "playing" is true (no way to detect when it actually starts in Flash unless onPlay is watched?) 494 var thisOptions = sm._mergeObjects(oOptions); 495 if (typeof thisOptions.url == 'undefined') thisOptions.url = self.url; 496 try { 497 sm._writeDebug('loading '+thisOptions.url,1); 498 sm.o._load(self.sID,thisOptions.url,thisOptions.stream,thisOptions.autoPlay,thisOptions.whileloading?1:0); 499 } catch(e) { 500 sm._writeDebug('SMSound().load(): JS->Flash communication failed.',2); 501 } 502 } 503 504 this.unload = function() { 505 // Flash 8/AS2 can't "close" a stream - fake it by loading an empty MP3 506 sm._writeDebug('SMSound().unload(): "'+self.sID+'"'); 507 self.setPosition(0); // reset current sound positioning 508 sm.o._unload(self.sID,sm.nullURL); 509 // reset load/status flags 510 self.resetProperties(); 511 } 512 513 this.play = function(oOptions) { 514 if (!oOptions) oOptions = {}; 515 516 // --- TODO: make event handlers specified via oOptions only apply to this instance of play() (eg. onfinish applies but will reset to default on finish) 517 if (oOptions.onfinish) self.options.onfinish = oOptions.onfinish; 518 if (oOptions.onbeforefinish) self.options.onbeforefinish = oOptions.onbeforefinish; 519 if (oOptions.onjustbeforefinish) self.options.onjustbeforefinish = oOptions.onjustbeforefinish; 520 // --- 521 522 var thisOptions = sm._mergeObjects(oOptions); 523 if (self.playState == 1) { 524 // var allowMulti = typeof oOptions.multiShot!='undefined'?oOptions.multiShot:sm.defaultOptions.multiShot; 525 var allowMulti = thisOptions.multiShot; 526 if (!allowMulti) { 527 sm._writeDebug('SMSound.play(): "'+self.sID+'" already playing? (one-shot)',1); 528 return false;
529 } else { 530 sm._writeDebug('SMSound.play(): "'+self.sID+'" already playing (multi-shot)',1); 531 } 532 } 533 if (!self.loaded) { 534 if (self.readyState == 0) { 535 sm._writeDebug('SMSound.play(): .play() before load request. Attempting to load "'+self.sID+'"',1); 536 // try to get this sound playing ASAP 537 thisOptions.stream = true; 538 thisOptions.autoPlay = true; 539 // TODO: need to investigate when false, double-playing 540 // if (typeof oOptions.autoPlay=='undefined') thisOptions.autoPlay = true; // only set autoPlay if unspecified here 541 self.load(thisOptions); // try to get this sound playing ASAP 542 } else if (self.readyState == 2) { 543 sm._writeDebug('SMSound.play(): Could not load "'+self.sID+'" - exiting',2); 544 return false; 545 } else { 546 sm._writeDebug('SMSound.play(): "'+self.sID+'" is loading - attempting to play..',1); 547 } 548 } else { 549 sm._writeDebug('SMSound.play(): "'+self.sID+'"'); 550 } 551 if (self.paused) { 552 self.resume(); 553 } else { 554 self.playState = 1; 555 self.position = (thisOptions.offset||0); 556 if (thisOptions.onplay) thisOptions.onplay.apply(self); 557 self.setVolume(thisOptions.volume); 558 self.setPan(thisOptions.pan); 559 if (!thisOptions.autoPlay) { 560 // sm._writeDebug('starting sound '+self.sID); 561 sm.o._start(self.sID,thisOptions.loop||1,self.position); // TODO: verify !autoPlay doesn't cause issue 562 } 563 } 564 } 565 566 this.start = this.play; // just for convenience 567 568 this.stop = function(bAll) { 569 if (self.playState == 1) { 570 self.playState = 0; 571 self.paused = false; 572 if (sm.defaultOptions.onstop) sm.defaultOptions.onstop.apply(self); 573 sm.o._stop(self.sID); 574 } 575 } 576 577 this.setPosition = function(nMsecOffset) { 578 // sm._writeDebug('setPosition('+nMsecOffset+')'); 579 sm.o._setPosition(self.sID,nMsecOffset/1000,self.paused||!self.playState); // if paused or not playing, will not resume (by playing) 580 } 581 582 this.pause = function() { 583 if (self.paused) return false; 584 sm._writeDebug('SMSound.pause()'); 585 self.paused = true; 586 sm.o._pause(self.sID); 587 } 588 589 this.resume = function() { 590 if (!self.paused) return false; 591 sm._writeDebug('SMSound.resume()'); 592 self.paused = false; 593 sm.o._pause(self.sID); // flash method is toggle-based (pause/resume) 594 } 595 596 this.togglePause = function() { 597 // if playing, pauses - if paused, resumes playing. 598 sm._writeDebug('SMSound.togglePause()'); 599 if (!self.playState) { 600 // self.setPosition(); 601 self.play({offset:self.position/1000}); 602 return false; 603 } 604 if (self.paused) { 605 sm._writeDebug('SMSound.togglePause(): resuming..'); 606 self.resume(); 607 } else { 608 sm._writeDebug('SMSound.togglePause(): pausing..'); 609 self.pause(); 610 } 611 } 612 613 this.setPan = function(nPan) { 614 if (typeof nPan == 'undefined') nPan = 0; 615 sm.o._setPan(self.sID,nPan); 616 self.options.pan = nPan; 617 } 618 619 this.setVolume = function(nVol) { 620 if (typeof nVol == 'undefined') nVol = 100; 621 sm.o._setVolume(self.sID,nVol); 622 self.options.volume = nVol; 623 } 624 625 // --- "private" methods called by Flash --- 626 627 this._whileloading = function(nBytesLoaded,nBytesTotal,nDuration) { 628 self.bytesLoaded = nBytesLoaded; 629 self.bytesTotal = nBytesTotal; 630 self.duration = nDuration; 631 self.durationEstimate = parseInt((self.bytesTotal/self.bytesLoaded)*self.duration); // estimate total time (will only be accurate with CBR MP3s.) 632 if (self.readyState != 3 && self.options.whileloading) self.options.whileloading.apply(self); 633 // soundManager._writeDebug('duration/durationEst: '+self.duration+' / '+self.durationEstimate); 634 } 635 636 this._onid3 = function(oID3PropNames,oID3Data) { 637 // oID3PropNames: string array (names) 638 // ID3Data: string array (data) 639 sm._writeDebug('SMSound()._onid3(): "'+this.sID+'" ID3 data received.'); 640 var oData = []; 641 for (var i=0,j=oID3PropNames.length; i<j; i++) { 642 oData[oID3PropNames[i]] = oID3Data[i]; 643 // sm._writeDebug(oID3PropNames[i]+': '+oID3Data[i]); 644 } 645 self.id3 = sm._mergeObjects(self.id3,oData); 646 if (self.options.onid3) self.options.onid3.apply(self); 647 } 648 649 this._whileplaying = function(nPosition) { 650 if (isNaN(nPosition) || nPosition == null) return false; // Flash may return NaN at times 651 self.position = nPosition; 652 if (self.playState == 1) { 653 if (self.options.whileplaying) self.options.whileplaying.apply(self); // flash may call after actual finish 654 if (self.loaded && self.options.onbeforefinish && self.options.onbeforefinishtime && !self.didBeforeFinish && self.duration-self.position <= self.options.onbeforefinishtime) { 655 sm._writeDebug('duration-position <= onbeforefinishtime: '+self.duration+' - '+self.position+' <= '+self.options.onbeforefinishtime+' ('+(self.duration-self.position)+')'); 656 self._onbeforefinish(); 657 } 658 } 659 } 660 661 this._onload = function(bSuccess) { 662 bSuccess = (bSuccess==1?true:false); 663 sm._writeDebug('SMSound._onload(): "'+self.sID+'"'+(bSuccess?' loaded.':' failed to load (or loaded from cache - weird bug) - [<a href="'+self.url+'">test URL</a>]')); 664 self.loaded = bSuccess; 665 self.loadSuccess = bSuccess; 666 self.readyState = bSuccess?3:2; 667 if (self.options.onload) self.options.onload.apply(self); 668 } 669 670 this._onbeforefinish = function() { 671 if (!self.didBeforeFinish) { 672 self.didBeforeFinish = true; 673 if (self.options.onbeforefinish) self.options.onbeforefinish.apply(self); 674 } 675 } 676 677 this._onjustbeforefinish = function(msOffset) { 678 // msOffset: "end of sound" delay actual value (eg. 200 msec, value at event fire time was 187) 679 if (!self.didJustBeforeFinish) { 680 self.didJustBeforeFinish = true; 681 // soundManager._writeDebug('SMSound._onjustbeforefinish()'); 682 if (self.options.onjustbeforefinish) self.options.onjustbeforefinish.apply(self);; 683 } 684 } 685 686 this._onfinish = function() { 687 // sound has finished playing 688 sm._writeDebug('SMSound._onfinish(): "'+self.sID+'"'); 689 self.playState = 0; 690 self.paused = false;
691 if (self.options.onfinish) self.options.onfinish.apply(self); 692 if (self.options.onbeforefinishcomplete) self.options.onbeforefinishcomplete.apply(self); 693 // reset some state items 694 self.setPosition(0); 695 self.didBeforeFinish = false; 696 self.didJustBeforeFinish = false; 697 } 698 699} 700 701var soundManager = new SoundManager(); 702 703// attach onload handler 704if (window.addEventListener) { 705 window.addEventListener('load',soundManager.beginDelayedInit,false); 706 window.addEventListener('beforeunload',soundManager.destruct,false); 707} else if (window.attachEvent) { 708 window.attachEvent('onload',soundManager.beginInit); 709 window.attachEvent('beforeunload',soundManager.destruct); 710} else { 711 // no add/attachevent support - safe to assume no JS->Flash either. 712 soundManager.onerror(); 713 soundManager.disable(); 714}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.