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