1/** @license 2 * 3 * SoundManager 2: JavaScript Sound for the Web 4 * ---------------------------------------------- 5 * http://schillmania.com/projects/soundmanager2/ 6 * 7 * Copyright (c) 2007, Scott Schiller. All rights reserved. 8 * Code provided under the BSD License: 9 * http://schillmania.com/projects/soundmanager2/license.txt 10 * 11 * V2.97a.20111220 12 */ 13 14/*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio */ 15/* jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true */ 16 17/** 18 * About this file 19 * --------------- 20 * This is the fully-commented source version of the SoundManager 2 API, 21 * recommended for use during development and testing. 22 * 23 * See soundmanager2-nodebug-jsmin.js for an optimized build (~10KB with gzip.) 24 * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion 25 * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.) 26 * 27 * You may notice <d> and </d> comments in this source; these are delimiters for 28 * debug blocks which are removed in the -nodebug builds, further optimizing code size. 29 * 30 * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;) 31 */ 32 33(function(window) { 34 35var soundManager = null; 36 37/** 38 * The SoundManager constructor. 39 * 40 * @constructor 41 * @param {string} smURL Optional: Path to SWF files 42 * @param {string} smID Optional: The ID to use for the SWF container element 43 * @this {SoundManager} 44 * @return {SoundManager} The new SoundManager instance 45 */ 46 47function SoundManager(smURL, smID) { 48 49 // Top-level configuration options 50 51 this.flashVersion = 8; // flash build to use (8 or 9.) Some API features require 9. 52 this.debugMode = true; // enable debugging output (console.log() with HTML fallback) 53 this.debugFlash = false; // enable debugging output inside SWF, troubleshoot Flash/browser issues 54 this.useConsole = true; // use console.log() if available (otherwise, writes to #soundmanager-debug element) 55 this.consoleOnly = true; // if console is being used, do not create/write to #soundmanager-debug 56 this.waitForWindowLoad = false; // force SM2 to wait for window.onload() before trying to call soundManager.onload() 57 this.bgColor = '#ffffff'; // SWF background color. N/A when wmode = 'transparent' 58 this.useHighPerformance = false; // position:fixed flash movie can help increase js/flash speed, minimize lag 59 this.flashPollingInterval = null; // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used. 60 this.html5PollingInterval = null; // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used. 61 this.flashLoadTimeout = 1000; // msec to wait for flash movie to load before failing (0 = infinity) 62 this.wmode = null; // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work) 63 this.allowScriptAccess = 'always'; // for scripting the SWF (object/embed property), 'always' or 'sameDomain' 64 this.useFlashBlock = false; // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable. 65 this.useHTML5Audio = true; // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible. 66 this.html5Test = /^(probably|maybe)$/i; // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative. 67 this.preferFlash = true; // overrides useHTML5audio. if true and flash support present, will try to use flash for MP3/MP4 as needed since HTML5 audio support is still quirky in browsers. 68 this.noSWFCache = false; // if true, appends ?ts={date} to break aggressive SWF caching. 69 70 this.audioFormats = { 71 72 /** 73 * determines HTML5 support + flash requirements. 74 * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start. 75 * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true) 76 * multiple MIME types may be tested while trying to get a positive canPlayType() response. 77 */ 78 79 'mp3': { 80 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'], 81 'required': true 82 }, 83 84 'mp4': { 85 'related': ['aac','m4a'], // additional formats under the MP4 container 86 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'], 87 'required': false 88 }, 89 90 'ogg': { 91 'type': ['audio/ogg; codecs=vorbis'], 92 'required': false 93 }, 94 95 'wav': { 96 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'], 97 'required': false 98 } 99 100 }; 101 102 this.defaultOptions = { 103 104 /** 105 * the default configuration for sound objects made with createSound() and related methods 106 * eg., volume, auto-load behaviour and so forth 107 */ 108 109 '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) 110 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true) 111 'from': null, // position to start playback within a sound (msec), default = beginning 112 'loops': 1, // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0) 113 'onid3': null, // callback function for "ID3 data is added/available" 114 'onload': null, // callback function for "load finished" 115 'whileloading': null, // callback function for "download progress update" (X of Y bytes received) 116 'onplay': null, // callback for "play" start 117 'onpause': null, // callback for "pause" 118 'onresume': null, // callback for "resume" (pause toggle) 119 'whileplaying': null, // callback during play (position update)
120 'onposition': null, // object containing times and function callbacks for positions of interest 121 'onstop': null, // callback for "user stop" 122 'onfailure': null, // callback function for when playing fails 123 'onfinish': null, // callback function for "sound finished playing" 124 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time 125 'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled 126 'position': null, // offset (milliseconds) to seek to within loaded sound data. 127 'pan': 0, // "pan" settings, left-to-right, -100 to 100 128 'stream': true, // allows playing before entire file has loaded (recommended) 129 'to': null, // position to end playback within a sound (msec), default = end 130 'type': null, // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3 131 'usePolicyFile': false, // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access) 132 'volume': 100 // self-explanatory. 0-100, the latter being the max. 133 134 }; 135 136 this.flash9Options = { 137 138 /** 139 * flash 9-only options, 140 * merged into defaultOptions if flash 9 is being used 141 */ 142 143 'isMovieStar': null, // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL 144 'usePeakData': false, // enable left/right channel peak (level) data 145 'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load. 146 'useEQData': false, // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load. 147 'onbufferchange': null, // callback for "isBuffering" property change 148 'ondataerror': null // callback for waveform/eq data access error (flash playing audio in other tabs/domains) 149 150 }; 151 152 this.movieStarOptions = { 153 154 /** 155 * flash 9.0r115+ MPEG4 audio options, 156 * merged into defaultOptions if flash 9+movieStar mode is enabled 157 */ 158 159 'bufferTime': 3, // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.) 160 'serverURL': null, // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants 161 'onconnect': null, // rtmp: callback for connection to flash media server 162 'duration': null // rtmp: song duration (msec) 163 164 }; 165 166 // HTML attributes (id + class names) for the SWF container 167 168 this.movieID = 'sm2-container'; 169 this.id = (smID || 'sm2movie'); 170 171 this.debugID = 'soundmanager-debug'; 172 this.debugURLParam = /([#?&])debug=1/i; 173 174 // dynamic attributes 175 176 this.versionNumber = 'V2.97a.20111220'; 177 this.version = null; 178 this.movieURL = null; 179 this.url = (smURL || null); 180 this.altURL = null; 181 this.swfLoaded = false; 182 this.enabled = false; 183 this.oMC = null; 184 this.sounds = {}; 185 this.soundIDs = []; 186 this.muted = false; 187 this.didFlashBlock = false; 188 this.filePattern = null; 189 190 this.filePatterns = { 191 192 'flash8': /\.mp3(\?.*)?$/i, 193 'flash9': /\.mp3(\?.*)?$/i 194 195 }; 196 197 // support indicators, set at init 198 199 this.features = { 200 201 'buffering': false, 202 'peakData': false, 203 'waveformData': false, 204 'eqData': false, 205 'movieStar': false 206 207 }; 208 209 // flash sandbox info, used primarily in troubleshooting 210 211 this.sandbox = { 212 213 // <d> 214 'type': null, 215 'types': { 216 'remote': 'remote (domain-based) rules', 217 'localWithFile': 'local with file access (no internet access)', 218 'localWithNetwork': 'local with network (internet access only, no local access)', 219 'localTrusted': 'local, trusted (local+internet access)' 220 }, 221 'description': null, 222 'noRemote': null, 223 'noLocal': null 224 // </d> 225 226 }; 227 228 /** 229 * basic HTML5 Audio() support test 230 * try...catch because of IE 9 "not implemented" nonsense 231 * https://github.com/Modernizr/Modernizr/issues/224 232 */ 233 234 this.hasHTML5 = (function() { 235 try { 236 return (typeof Audio !== 'undefined' && typeof new Audio().canPlayType !== 'undefined'); 237 } catch(e) { 238 return false;
239 } 240 }()); 241 242 /** 243 * format support (html5/flash) 244 * stores canPlayType() results based on audioFormats. 245 * eg. { mp3: boolean, mp4: boolean } 246 * treat as read-only. 247 */ 248 249 this.html5 = { 250 'usingFlash': null // set if/when flash fallback is needed 251 }; 252 253 this.flash = {}; // file type support hash 254 255 this.html5Only = false; // determined at init time 256 this.ignoreFlash = false; // used for special cases (eg. iPad/iPhone/palm OS?) 257 258 /** 259 * a few private internals (OK, a lot. :D) 260 */ 261 262 var SMSound, 263 _s = this, _flash = null, _sm = 'soundManager', _smc = _sm+'::', _h5 = 'HTML5::', _id, _ua = navigator.userAgent, _win = window, _wl = _win.location.href.toString(), _doc = document, _doNothing, _init, _fV, _on_queue = [], _debugOpen = true, _debugTS, _didAppend = false, _appendSuccess = false, _didInit = false, _disabled = false, _windowLoaded = false, _wDS, _wdCount = 0, _initComplete, _mixin, _addOnEvent, _processOnEvents, _initUserOnload, _delayWaitForEI, _waitForEI, _setVersionInfo, _handleFocus, _strings, _initMovie, _domContentLoaded, _winOnLoad, _didDCLoaded, _getDocument, _createMovie, _catchError, _setPolling, _initDebug, _debugLevels = ['log', 'info', 'warn', 'error'], _defaultFlashVersion = 8, _disableObject, _failSafely, _normalizeMovieURL, _oRemoved = null, _oRemovedHTML = null, _str, _flashBlockHandler, _getSWFCSS, _swfCSS, _toggleDebug, _loopFix, _policyFix, _complain, _idCheck, _waitingForEI = false, _initPending = false, _smTimer, _onTimer, _startTimer, _stopTimer, _timerExecute, _h5TimerCount = 0, _h5IntervalTimer = null, _parseURL, 264 _needsFlash = null, _featureCheck, _html5OK, _html5CanPlay, _html5Ext, _html5Unload, _domContentLoadedIE, _testHTML5, _event, _slice = Array.prototype.slice, _useGlobalHTML5Audio = false, _hasFlash, _detectFlash, _badSafariFix, _html5_events, _showSupport, 265 _is_iDevice = _ua.match(/(ipad|iphone|ipod)/i), _is_firefox = _ua.match(/firefox/i), _is_android = _ua.match(/droid/i), _isIE = _ua.match(/msie/i), _isWebkit = _ua.match(/webkit/i), _isSafari = (_ua.match(/safari/i) && !_ua.match(/chrome/i)), _isOpera = (_ua.match(/opera/i)), 266 _likesHTML5 = (_ua.match(/(mobile|pre\/|xoom)/i) || _is_iDevice), 267 _isBadSafari = (!_wl.match(/usehtml5audio/i) && !_wl.match(/sm2\-ignorebadua/i) && _isSafari && !_ua.match(/silk/i) && _ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159 268 _hasConsole = (typeof console !== 'undefined' && typeof console.log !== 'undefined'), _isFocused = (typeof _doc.hasFocus !== 'undefined'?_doc.hasFocus():null), _tryInitOnFocus = (_isSafari && typeof _doc.hasFocus === 'undefined'), _okToDisable = !_tryInitOnFocus, _flashMIME = /(mp3|mp4|mpa)/i, 269 _emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs) 270 _overHTTP = (_doc.location?_doc.location.protocol.match(/http/i):null), 271 _http = (!_overHTTP ? 'http:/'+'/' : ''), 272 // mp3, mp4, aac etc. 273 _netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|mp4v|3gp|3g2)\s*(?:$|;)/i, 274 // Flash v9.0r115+ "moviestar" formats 275 _netStreamTypes = ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'mp4v', '3gp', '3g2'], 276 _netStreamPattern = new RegExp('\\.(' + _netStreamTypes.join('|') + ')(\\?.*)?$', 'i'); 277 278 this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set 279 280 // use altURL if not "online" 281 this.useAltURL = !_overHTTP; 282 this._global_a = null; 283 284 _swfCSS = { 285 286 'swfBox': 'sm2-object-box', 287 'swfDefault': 'movieContainer', 288 'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error) 289 'swfTimedout': 'swf_timedout', 290 'swfLoaded': 'swf_loaded', 291 'swfUnblocked': 'swf_unblocked', // or loaded OK 292 'sm2Debug': 'sm2_debug', 293 'highPerf': 'high_performance', 294 'flashDebug': 'flash_debug' 295 296 }; 297 298 if (_likesHTML5) { 299 300 // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point. 301 _s.useHTML5Audio = true; 302 _s.preferFlash = false;
303 304 if (_is_iDevice) { 305 // by default, use global feature. iOS onfinish() -> next may fail otherwise. 306 _s.ignoreFlash = true; 307 _useGlobalHTML5Audio = true; 308 } 309 310 } 311 312 /** 313 * Public SoundManager API 314 * ----------------------- 315 */ 316 317 this.ok = function() { 318 319 return (_needsFlash?(_didInit && !_disabled):(_s.useHTML5Audio && _s.hasHTML5)); 320 321 }; 322 323 this.supported = this.ok; // legacy 324 325 this.getMovie = function(smID) { 326 327 // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version 328 return _id(smID) || _doc[smID] || _win[smID]; 329 330 }; 331 332 /** 333 * Creates a SMSound sound object instance. 334 * 335 * @param {object} oOptions Sound options (at minimum, id and url are required.) 336 * @return {object} SMSound The new SMSound object. 337 */ 338 339 this.createSound = function(oOptions) { 340 341 var _cs, _cs_string, 342 thisOptions = null, oSound = null, _tO = null; 343 344 // <d> 345 _cs = _sm+'.createSound(): '; 346 _cs_string = _cs + _str(!_didInit?'notReady':'notOK'); 347 // </d> 348 349 if (!_didInit || !_s.ok()) { 350 _complain(_cs_string); 351 return false; 352 } 353 354 if (arguments.length === 2) { 355 // function overloading in JS! :) ..assume simple createSound(id,url) use case 356 oOptions = { 357 'id': arguments[0], 358 'url': arguments[1] 359 }; 360 } 361 362 // inherit from defaultOptions 363 thisOptions = _mixin(oOptions); 364 365 thisOptions.url = _parseURL(thisOptions.url); 366 367 // local shortcut 368 _tO = thisOptions; 369 370 // <d> 371 if (_tO.id.toString().charAt(0).match(/^[0-9]$/)) { 372 _s._wD(_cs + _str('badID', _tO.id), 2); 373 } 374 375 _s._wD(_cs + _tO.id + ' (' + _tO.url + ')', 1); 376 // </d> 377 378 if (_idCheck(_tO.id, true)) { 379 _s._wD(_cs + _tO.id + ' exists', 1); 380 return _s.sounds[_tO.id]; 381 } 382 383 function make() { 384 385 thisOptions = _loopFix(thisOptions); 386 _s.sounds[_tO.id] = new SMSound(_tO); 387 _s.soundIDs.push(_tO.id); 388 return _s.sounds[_tO.id]; 389 390 } 391 392 if (_html5OK(_tO)) { 393 394 oSound = make(); 395 _s._wD('Loading sound '+_tO.id+' via HTML5'); 396 oSound._setup_html5(_tO); 397 398 } else { 399 400 if (_fV > 8) { 401 if (_tO.isMovieStar === null) { 402 // attempt to detect MPEG-4 formats 403 _tO.isMovieStar = (_tO.serverURL || (_tO.type ? _tO.type.match(_netStreamMimeTypes) : false) || _tO.url.match(_netStreamPattern)); 404 } 405 // <d> 406 if (_tO.isMovieStar) { 407 _s._wD(_cs + 'using MovieStar handling'); 408 } 409 // </d> 410 if (_tO.isMovieStar) { 411 if (_tO.usePeakData) { 412 _wDS('noPeak'); 413 _tO.usePeakData = false; 414 } 415 // <d> 416 if (_tO.loops > 1) { 417 _wDS('noNSLoop'); 418 } 419 // </d> 420 } 421 } 422 423 _tO = _policyFix(_tO, _cs); 424 oSound = make(); 425 426 if (_fV === 8) { 427 _flash._createSound(_tO.id, _tO.loops||1, _tO.usePolicyFile); 428 } else { 429 _flash._createSound(_tO.id, _tO.url, _tO.usePeakData, _tO.useWaveformData, _tO.useEQData, _tO.isMovieStar, (_tO.isMovieStar?_tO.bufferTime:false), _tO.loops||1, _tO.serverURL, _tO.duration||null, _tO.autoPlay, true, _tO.autoLoad, _tO.usePolicyFile); 430 if (!_tO.serverURL) { 431 // We are connected immediately 432 oSound.connected = true; 433 if (_tO.onconnect) { 434 _tO.onconnect.apply(oSound); 435 } 436 } 437 } 438 439 if (!_tO.serverURL && (_tO.autoLoad || _tO.autoPlay)) { 440 // call load for non-rtmp streams 441 oSound.load(_tO); 442 } 443 444 } 445 446 // rtmp will play in onconnect 447 if (!_tO.serverURL && _tO.autoPlay) { 448 oSound.play(); 449 } 450 451 return oSound; 452 453 }; 454 455 /** 456 * Destroys a SMSound sound object instance. 457 * 458 * @param {string} sID The ID of the sound to destroy 459 */ 460 461 this.destroySound = function(sID, _bFromSound) { 462 463 // explicitly destroy a sound before normal page unload, etc. 464 465 if (!_idCheck(sID)) { 466 return false; 467 } 468 469 var oS = _s.sounds[sID], i; 470 471 // Disable all callbacks while the sound is being destroyed 472 oS._iO = {}; 473 474 oS.stop(); 475 oS.unload(); 476 477 for (i = 0; i < _s.soundIDs.length; i++) { 478 if (_s.soundIDs[i] === sID) { 479 _s.soundIDs.splice(i, 1); 480 break; 481 } 482 } 483 484 if (!_bFromSound) { 485 // ignore if being called from SMSound instance 486 oS.destruct(true); 487 } 488 489 oS = null; 490 delete _s.sounds[sID]; 491 492 return true; 493 494 }; 495 496 /** 497 * Calls the load() method of a SMSound object by ID. 498 * 499 * @param {string} sID The ID of the sound 500 * @param {object} oOptions Optional: Sound options 501 */ 502 503 this.load = function(sID, oOptions) { 504
505 if (!_idCheck(sID)) { 506 return false; 507 } 508 return _s.sounds[sID].load(oOptions); 509 510 }; 511 512 /** 513 * Calls the unload() method of a SMSound object by ID. 514 * 515 * @param {string} sID The ID of the sound 516 */ 517 518 this.unload = function(sID) { 519 520 if (!_idCheck(sID)) { 521 return false; 522 } 523 return _s.sounds[sID].unload(); 524 525 }; 526 527 /** 528 * Calls the onPosition() method of a SMSound object by ID. 529 * 530 * @param {string} sID The ID of the sound 531 * @param {number} nPosition The position to watch for 532 * @param {function} oMethod The relevant callback to fire 533 * @param {object} oScope Optional: The scope to apply the callback to 534 * @return {SMSound} The SMSound object 535 */ 536 537 this.onPosition = function(sID, nPosition, oMethod, oScope) { 538 539 if (!_idCheck(sID)) { 540 return false; 541 } 542 return _s.sounds[sID].onposition(nPosition, oMethod, oScope); 543 544 }; 545 546 // legacy/backwards-compability: lower-case method name 547 this.onposition = this.onPosition; 548 549 /** 550 * Calls the clearOnPosition() method of a SMSound object by ID. 551 * 552 * @param {string} sID The ID of the sound 553 * @param {number} nPosition The position to watch for 554 * @param {function} oMethod Optional: The relevant callback to fire 555 * @return {SMSound} The SMSound object 556 */ 557 558 this.clearOnPosition = function(sID, nPosition, oMethod) { 559 560 if (!_idCheck(sID)) { 561 return false; 562 } 563 return _s.sounds[sID].clearOnPosition(nPosition, oMethod); 564 565 }; 566 567 /** 568 * Calls the play() method of a SMSound object by ID. 569 * 570 * @param {string} sID The ID of the sound 571 * @param {object} oOptions Optional: Sound options 572 * @return {SMSound} The SMSound object 573 */ 574 575 this.play = function(sID, oOptions) { 576 577 if (!_didInit || !_s.ok()) { 578 _complain(_sm+'.play(): ' + _str(!_didInit?'notReady':'notOK')); 579 return false; 580 } 581 582 if (!_idCheck(sID)) { 583 if (!(oOptions instanceof Object)) { 584 // overloading use case: play('mySound','/path/to/some.mp3'); 585 oOptions = { 586 url: oOptions 587 }; 588 } 589 if (oOptions && oOptions.url) { 590 // overloading use case, create+play: .play('someID',{url:'/path/to.mp3'}); 591 _s._wD(_sm+'.play(): attempting to create "' + sID + '"', 1); 592 oOptions.id = sID; 593 return _s.createSound(oOptions).play(); 594 } else { 595 return false; 596 } 597 } 598 599 return _s.sounds[sID].play(oOptions); 600 601 }; 602 603 this.start = this.play; // just for convenience 604 605 /** 606 * Calls the setPosition() method of a SMSound object by ID. 607 * 608 * @param {string} sID The ID of the sound 609 * @param {number} nMsecOffset Position (milliseconds) 610 * @return {SMSound} The SMSound object 611 */ 612 613 this.setPosition = function(sID, nMsecOffset) { 614 615 if (!_idCheck(sID)) { 616 return false; 617 } 618 return _s.sounds[sID].setPosition(nMsecOffset); 619 620 }; 621 622 /** 623 * Calls the stop() method of a SMSound object by ID. 624 * 625 * @param {string} sID The ID of the sound 626 * @return {SMSound} The SMSound object 627 */ 628 629 this.stop = function(sID) { 630 631 if (!_idCheck(sID)) { 632 return false; 633 } 634 635 _s._wD(_sm+'.stop(' + sID + ')', 1); 636 return _s.sounds[sID].stop(); 637 638 }; 639 640 /** 641 * Stops all currently-playing sounds. 642 */ 643 644 this.stopAll = function() { 645 646 var oSound; 647 _s._wD(_sm+'.stopAll()', 1); 648 649 for (oSound in _s.sounds) { 650 if (_s.sounds.hasOwnProperty(oSound)) { 651 // apply only to sound objects 652 _s.sounds[oSound].stop(); 653 } 654 } 655 656 }; 657 658 /** 659 * Calls the pause() method of a SMSound object by ID. 660 * 661 * @param {string} sID The ID of the sound 662 * @return {SMSound} The SMSound object 663 */ 664 665 this.pause = function(sID) { 666 667 if (!_idCheck(sID)) { 668 return false; 669 } 670 return _s.sounds[sID].pause(); 671 672 }; 673 674 /** 675 * Pauses all currently-playing sounds. 676 */ 677 678 this.pauseAll = function() { 679 680 var i; 681 for (i = _s.soundIDs.length; i--;) { 682 _s.sounds[_s.soundIDs[i]].pause(); 683 } 684 685 }; 686 687 /** 688 * Calls the resume() method of a SMSound object by ID. 689 * 690 * @param {string} sID The ID of the sound 691 * @return {SMSound} The SMSound object 692 */ 693 694 this.resume = function(sID) { 695
696 if (!_idCheck(sID)) { 697 return false; 698 } 699 return _s.sounds[sID].resume(); 700 701 }; 702 703 /** 704 * Resumes all currently-paused sounds. 705 */ 706 707 this.resumeAll = function() { 708 709 var i; 710 for (i = _s.soundIDs.length; i--;) { 711 _s.sounds[_s.soundIDs[i]].resume(); 712 } 713 714 }; 715 716 /** 717 * Calls the togglePause() method of a SMSound object by ID. 718 * 719 * @param {string} sID The ID of the sound 720 * @return {SMSound} The SMSound object 721 */ 722 723 this.togglePause = function(sID) { 724 725 if (!_idCheck(sID)) { 726 return false; 727 } 728 return _s.sounds[sID].togglePause(); 729 730 }; 731 732 /** 733 * Calls the setPan() method of a SMSound object by ID. 734 * 735 * @param {string} sID The ID of the sound 736 * @param {number} nPan The pan value (-100 to 100) 737 * @return {SMSound} The SMSound object 738 */ 739 740 this.setPan = function(sID, nPan) { 741 742 if (!_idCheck(sID)) { 743 return false; 744 } 745 return _s.sounds[sID].setPan(nPan); 746 747 }; 748 749 /** 750 * Calls the setVolume() method of a SMSound object by ID. 751 * 752 * @param {string} sID The ID of the sound 753 * @param {number} nVol The volume value (0 to 100) 754 * @return {SMSound} The SMSound object 755 */ 756 757 this.setVolume = function(sID, nVol) { 758 759 if (!_idCheck(sID)) { 760 return false; 761 } 762 return _s.sounds[sID].setVolume(nVol); 763 764 }; 765 766 /** 767 * Calls the mute() method of either a single SMSound object by ID, or all sound objects. 768 * 769 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.) 770 */ 771 772 this.mute = function(sID) { 773 774 var i = 0; 775 776 if (typeof sID !== 'string') { 777 sID = null; 778 } 779 780 if (!sID) { 781 _s._wD(_sm+'.mute(): Muting all sounds'); 782 for (i = _s.soundIDs.length; i--;) { 783 _s.sounds[_s.soundIDs[i]].mute(); 784 } 785 _s.muted = true; 786 } else { 787 if (!_idCheck(sID)) { 788 return false; 789 } 790 _s._wD(_sm+'.mute(): Muting "' + sID + '"'); 791 return _s.sounds[sID].mute(); 792 } 793 794 return true; 795 796 }; 797 798 /** 799 * Mutes all sounds. 800 */ 801 802 this.muteAll = function() { 803 804 _s.mute(); 805 806 }; 807 808 /** 809 * Calls the unmute() method of either a single SMSound object by ID, or all sound objects. 810 * 811 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.) 812 */ 813 814 this.unmute = function(sID) { 815 816 var i; 817 818 if (typeof sID !== 'string') { 819 sID = null; 820 } 821 822 if (!sID) { 823 824 _s._wD(_sm+'.unmute(): Unmuting all sounds'); 825 for (i = _s.soundIDs.length; i--;) { 826 _s.sounds[_s.soundIDs[i]].unmute(); 827 } 828 _s.muted = false; 829 830 } else { 831 832 if (!_idCheck(sID)) { 833 return false; 834 } 835 _s._wD(_sm+'.unmute(): Unmuting "' + sID + '"'); 836 return _s.sounds[sID].unmute(); 837 838 } 839 840 return true; 841 842 }; 843 844 /** 845 * Unmutes all sounds. 846 */ 847 848 this.unmuteAll = function() { 849 850 _s.unmute(); 851 852 }; 853 854 /** 855 * Calls the toggleMute() method of a SMSound object by ID. 856 * 857 * @param {string} sID The ID of the sound 858 * @return {SMSound} The SMSound object 859 */ 860 861 this.toggleMute = function(sID) { 862 863 if (!_idCheck(sID)) { 864 return false; 865 } 866 return _s.sounds[sID].toggleMute(); 867 868 }; 869 870 /** 871 * Retrieves the memory used by the flash plugin. 872 * 873 * @return {number} The amount of memory in use 874 */ 875 876 this.getMemoryUse = function() { 877 878 // flash-only 879 var ram = 0; 880 881 if (_flash && _fV !== 8) { 882 ram = parseInt(_flash._getMemoryUse(), 10); 883 } 884 885 return ram; 886 887 }; 888 889 /** 890 * Undocumented: NOPs soundManager and all SMSound objects. 891 */ 892 893 this.disable = function(bNoDisable) { 894 895 // destroy all functions 896 var i; 897 898 if (typeof bNoDisable === 'undefined') { 899 bNoDisable = false; 900 } 901 902 if (_disabled) { 903 return false; 904 } 905 906 _disabled = true; 907 _wDS('shutdown', 1); 908 909 for (i = _s.soundIDs.length; i--;) { 910 _disableObject(_s.sounds[_s.soundIDs[i]]); 911 } 912 913 // fire "complete", despite fail 914 _initComplete(bNoDisable); 915 _event.remove(_win, 'load', _initUserOnload); 916 917 return true; 918 919 }; 920 921 /** 922 * Determines playability of a MIME type, eg. 'audio/mp3'. 923 */ 924 925 this.canPlayMIME = function(sMIME) { 926 927 var result; 928 929 if (_s.hasHTML5) { 930 result = _html5CanPlay({type:sMIME}); 931 } 932 933 if (!_needsFlash || result) { 934 // no flash, or OK 935 return result; 936 } else { 937 // if flash 9, test netStream (movieStar) types as well. 938 return (sMIME ? !!((_fV > 8 ? sMIME.match(_netStreamMimeTypes) : null) || sMIME.match(_s.mimePattern)) : null); 939 } 940 941 }; 942 943 /** 944 * Determines playability of a URL based on audio support. 945 * 946 * @param {string} sURL The URL to test 947 * @return {boolean} URL playability 948 */ 949 950 this.canPlayURL = function(sURL) { 951 952 var result; 953 954 if (_s.hasHTML5) { 955 result = _html5CanPlay({url: sURL}); 956 } 957 958 if (!_needsFlash || result) { 959 // no flash, or OK 960 return result; 961 } else { 962 return (sURL ? !!(sURL.match(_s.filePattern)) : null); 963 } 964 965 }; 966 967 /** 968 * Determines playability of an HTML DOM <a> object (or similar object literal) based on audio support. 969 * 970 * @param {object} oLink an HTML DOM <a> object or object literal including href and/or type attributes 971 * @return {boolean} URL playability 972 */ 973 974 this.canPlayLink = function(oLink) { 975 976 if (typeof oLink.type !== 'undefined' && oLink.type) { 977 if (_s.canPlayMIME(oLink.type)) { 978 return true; 979 } 980 } 981 982 return _s.canPlayURL(oLink.href); 983 984 }; 985 986 /** 987 * Retrieves a SMSound object by ID. 988 * 989 * @param {string} sID The ID of the sound 990 * @return {SMSound} The SMSound object 991 */ 992
993 this.getSoundById = function(sID, _suppressDebug) { 994 995 if (!sID) { 996 throw new Error(_sm+'.getSoundById(): sID is null/undefined'); 997 } 998 999 var result = _s.sounds[sID]; 1000 1001 // <d> 1002 if (!result && !_suppressDebug) { 1003 _s._wD('"' + sID + '" is an invalid sound ID.', 2); 1004 } 1005 // </d> 1006 1007 return result; 1008 1009 }; 1010 1011 /** 1012 * Queues a callback for execution when SoundManager has successfully initialized. 1013 * 1014 * @param {function} oMethod The callback method to fire 1015 * @param {object} oScope Optional: The scope to apply to the callback 1016 */ 1017 1018 this.onready = function(oMethod, oScope) { 1019 1020 var sType = 'onready'; 1021 1022 if (oMethod && oMethod instanceof Function) { 1023 1024 // <d> 1025 if (_didInit) { 1026 _s._wD(_str('queue', sType)); 1027 } 1028 // </d> 1029 1030 if (!oScope) { 1031 oScope = _win; 1032 } 1033 1034 _addOnEvent(sType, oMethod, oScope); 1035 _processOnEvents(); 1036 1037 return true; 1038 1039 } else { 1040 1041 throw _str('needFunction', sType); 1042 1043 } 1044 1045 }; 1046 1047 /** 1048 * Queues a callback for execution when SoundManager has failed to initialize. 1049 * 1050 * @param {function} oMethod The callback method to fire 1051 * @param {object} oScope Optional: The scope to apply to the callback 1052 */ 1053 1054 this.ontimeout = function(oMethod, oScope) { 1055 1056 var sType = 'ontimeout'; 1057 1058 if (oMethod && oMethod instanceof Function) { 1059 1060 // <d> 1061 if (_didInit) { 1062 _s._wD(_str('queue', sType)); 1063 } 1064 // </d> 1065 1066 if (!oScope) { 1067 oScope = _win; 1068 } 1069 1070 _addOnEvent(sType, oMethod, oScope); 1071 _processOnEvents({type:sType}); 1072 1073 return true; 1074 1075 } else { 1076 1077 throw _str('needFunction', sType); 1078 1079 } 1080 1081 }; 1082 1083 /** 1084 * Writes console.log()-style debug output to a console or in-browser element. 1085 * Applies when SoundManager.debugMode = true 1086 * 1087 * @param {string} sText The console message 1088 * @param {string} sType Optional: Log type of 'info', 'warn' or 'error' 1089 * @param {object} Optional: The scope to apply to the callback 1090 */ 1091 1092 this._writeDebug = function(sText, sType, _bTimestamp) { 1093 1094 // pseudo-private console.log()-style output 1095 // <d> 1096 1097 var sDID = 'soundmanager-debug', o, oItem, sMethod; 1098 1099 if (!_s.debugMode) { 1100 return false; 1101 } 1102 1103 if (typeof _bTimestamp !== 'undefined' && _bTimestamp) { 1104 sText = sText + ' | ' + new Date().getTime(); 1105 } 1106 1107 if (_hasConsole && _s.useConsole) { 1108 sMethod = _debugLevels[sType]; 1109 if (typeof console[sMethod] !== 'undefined') { 1110 console[sMethod](sText); 1111 } else { 1112 console.log(sText); 1113 } 1114 if (_s.consoleOnly) { 1115 return true; 1116 } 1117 } 1118 1119 try { 1120 1121 o = _id(sDID); 1122 1123 if (!o) { 1124 return false; 1125 } 1126 1127 oItem = _doc.createElement('div'); 1128 1129 if (++_wdCount % 2 === 0) { 1130 oItem.className = 'sm2-alt'; 1131 } 1132 1133 if (typeof sType === 'undefined') { 1134 sType = 0; 1135 } else { 1136 sType = parseInt(sType, 10); 1137 } 1138 1139 oItem.appendChild(_doc.createTextNode(sText)); 1140 1141 if (sType) { 1142 if (sType >= 2) { 1143 oItem.style.fontWeight = 'bold'; 1144 } 1145 if (sType === 3) { 1146 oItem.style.color = '#ff3333'; 1147 } 1148 } 1149 1150 // top-to-bottom 1151 // o.appendChild(oItem); 1152 1153 // bottom-to-top 1154 o.insertBefore(oItem, o.firstChild); 1155 1156 } catch(e) { 1157 // oh well 1158 } 1159 1160 o = null; 1161 // </d> 1162 1163 return true; 1164 1165 }; 1166 1167 // alias 1168 this._wD = this._writeDebug; 1169 1170 /** 1171 * Provides debug / state information on all SMSound objects. 1172 */ 1173 1174 this._debug = function() { 1175 1176 // <d> 1177 var i, j; 1178 _wDS('currentObj', 1); 1179 1180 for (i = 0, j = _s.soundIDs.length; i < j; i++) { 1181 _s.sounds[_s.soundIDs[i]]._debug(); 1182 } 1183 // </d> 1184 1185 }; 1186 1187 /** 1188 * Restarts and re-initializes the SoundManager instance. 1189 */ 1190 1191 this.reboot = function() { 1192 1193 // attempt to reset and init SM2 1194 _s._wD(_sm+'.reboot()'); 1195 1196 // <d> 1197 if (_s.soundIDs.length) {
1198 _s._wD('Destroying ' + _s.soundIDs.length + ' SMSound objects...'); 1199 } 1200 // </d> 1201 1202 var i, j; 1203 1204 for (i = _s.soundIDs.length; i--;) { 1205 _s.sounds[_s.soundIDs[i]].destruct(); 1206 } 1207 1208 // trash ze flash 1209 1210 try { 1211 if (_isIE) { 1212 _oRemovedHTML = _flash.innerHTML; 1213 } 1214 _oRemoved = _flash.parentNode.removeChild(_flash); 1215 _s._wD('Flash movie removed.'); 1216 } catch(e) { 1217 // uh-oh. 1218 _wDS('badRemove', 2); 1219 } 1220 1221 // actually, force recreate of movie. 1222 _oRemovedHTML = _oRemoved = _needsFlash = null; 1223 1224 _s.enabled = _didDCLoaded = _didInit = _waitingForEI = _initPending = _didAppend = _appendSuccess = _disabled = _s.swfLoaded = false; 1225 _s.soundIDs = _s.sounds = []; 1226 _flash = null; 1227 1228 for (i in _on_queue) { 1229 if (_on_queue.hasOwnProperty(i)) { 1230 for (j = _on_queue[i].length; j--;) { 1231 _on_queue[i][j].fired = false; 1232 } 1233 } 1234 } 1235 1236 _s._wD(_sm + ': Rebooting...'); 1237 _win.setTimeout(_s.beginDelayedInit, 20); 1238 1239 }; 1240 1241 /** 1242 * Undocumented: Determines the SM2 flash movie's load progress. 1243 * 1244 * @return {number or null} Percent loaded, or if invalid/unsupported, null. 1245 */ 1246 1247 this.getMoviePercent = function() { 1248 1249 return (_flash && typeof _flash.PercentLoaded !== 'undefined' ? _flash.PercentLoaded() : null); 1250 1251 }; 1252 1253 /** 1254 * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload(). 1255 */ 1256 1257 this.beginDelayedInit = function() { 1258 1259 _windowLoaded = true; 1260 _domContentLoaded(); 1261 1262 setTimeout(function() { 1263 1264 if (_initPending) { 1265 return false; 1266 } 1267 1268 _createMovie(); 1269 _initMovie(); 1270 _initPending = true; 1271 1272 return true; 1273 1274 }, 20); 1275 1276 _delayWaitForEI(); 1277 1278 }; 1279 1280 /** 1281 * Destroys the SoundManager instance and all SMSound instances. 1282 */ 1283 1284 this.destruct = function() { 1285 1286 _s._wD(_sm+'.destruct()'); 1287 _s.disable(true); 1288 1289 }; 1290 1291 /** 1292 * SMSound() (sound object) constructor 1293 * ------------------------------------ 1294 * 1295 * @param {object} oOptions Sound options (id and url are required attributes) 1296 * @return {SMSound} The new SMSound object 1297 */ 1298 1299 SMSound = function(oOptions) { 1300 1301 var _t = this, _resetProperties, _add_html5_events, _remove_html5_events, _stop_html5_timer, _start_html5_timer, _attachOnPosition, _onplay_called = false, _onPositionItems = [], _onPositionFired = 0, _detachOnPosition, _applyFromTo, _lastURL = null; 1302 1303 var _lastHTML5State = { 1304 // tracks duration + position (time) 1305 duration: null, 1306 time: null 1307 }; 1308 1309 this.sID = oOptions.id; 1310 this.url = oOptions.url; 1311 this.options = _mixin(oOptions); 1312 1313 // per-play-instance-specific options 1314 this.instanceOptions = this.options; 1315 1316 // short alias 1317 this._iO = this.instanceOptions; 1318 1319 // assign property defaults 1320 this.pan = this.options.pan; 1321 this.volume = this.options.volume; 1322 this.isHTML5 = false; 1323 this._a = null; 1324 1325 /** 1326 * SMSound() public methods 1327 * ------------------------ 1328 */ 1329 1330 this.id3 = {}; 1331 1332 /** 1333 * Writes SMSound object parameters to debug console 1334 */ 1335 1336 this._debug = function() { 1337 1338 // <d> 1339 // pseudo-private console.log()-style output 1340 1341 if (_s.debugMode) { 1342 1343 var stuff = null, msg = [], sF, sfBracket, maxLength = 64; 1344 1345 for (stuff in _t.options) { 1346 if (_t.options[stuff] !== null) { 1347 if (_t.options[stuff] instanceof Function) { 1348 // handle functions specially 1349 sF = _t.options[stuff].toString(); 1350 // normalize spaces 1351 sF = sF.replace(/\s\s+/g, ' '); 1352 sfBracket = sF.indexOf('{'); 1353 msg.push(' ' + stuff + ': {' + sF.substr(sfBracket + 1, (Math.min(Math.max(sF.indexOf('\n') - 1, maxLength), maxLength))).replace(/\n/g, '') + '... }'); 1354 } else { 1355 msg.push(' ' + stuff + ': ' + _t.options[stuff]); 1356 } 1357 } 1358 } 1359 1360 _s._wD('SMSound() merged options: {\n' + msg.join(', \n') + '\n}'); 1361 1362 } 1363 // </d> 1364 1365 }; 1366 1367 // <d> 1368 this._debug(); 1369 // </d> 1370 1371 /** 1372 * Begins loading a sound per its *url*. 1373 * 1374 * @param {object} oOptions Optional: Sound options 1375 * @return {SMSound} The SMSound object 1376 */ 1377 1378 this.load = function(oOptions) { 1379 1380 var oS = null, _iO; 1381 1382 if (typeof oOptions !== 'undefined') { 1383 _t._iO = _mixin(oOptions, _t.options); 1384 _t.instanceOptions = _t._iO; 1385 } else { 1386 oOptions = _t.options; 1387 _t._iO = oOptions; 1388 _t.instanceOptions = _t._iO; 1389 if (_lastURL && _lastURL !== _t.url) { 1390 _wDS('manURL'); 1391 _t._iO.url = _t.url; 1392 _t.url = null; 1393 } 1394 } 1395 1396 if (!_t._iO.url) { 1397 _t._iO.url = _t.url; 1398 } 1399 1400 _t._iO.url = _parseURL(_t._iO.url); 1401 1402 _s._wD('SMSound.load(): ' + _t._iO.url, 1); 1403 1404 if (_t._iO.url === _t.url && _t.readyState !== 0 && _t.readyState !== 2) { 1405 _wDS('onURL', 1); 1406 // if loaded and an onload() exists, fire immediately. 1407 if (_t.readyState === 3 && _t._iO.onload) { 1408 // assume success based on truthy duration. 1409 _t._iO.onload.apply(_t, [(!!_t.duration)]); 1410 } 1411 return _t; 1412 } 1413 1414 // local shortcut 1415 _iO = _t._iO; 1416 1417 _lastURL = _t.url; 1418 _t.loaded = false;
1419 _t.readyState = 1; 1420 _t.playState = 0; 1421 1422 // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio. 1423 1424 if (_html5OK(_iO)) { 1425 1426 oS = _t._setup_html5(_iO); 1427 1428 if (!oS._called_load) { 1429 1430 _s._wD(_h5+'load: '+_t.sID); 1431 _t._html5_canplay = false; 1432 1433 // given explicit load call, try to get whole file. 1434 // early HTML5 implementation (non-standard) 1435 _t._a.autobuffer = 'auto'; 1436 // standard 1437 _t._a.preload = 'auto'; 1438 1439 oS.load(); 1440 oS._called_load = true; 1441 1442 if (_iO.autoPlay) { 1443 _t.play(); 1444 } 1445 1446 } else { 1447 _s._wD(_h5+'ignoring request to load again: '+_t.sID); 1448 } 1449 1450 } else { 1451 1452 try { 1453 _t.isHTML5 = false; 1454 _t._iO = _policyFix(_loopFix(_iO)); 1455 // re-assign local shortcut 1456 _iO = _t._iO; 1457 if (_fV === 8) { 1458 _flash._load(_t.sID, _iO.url, _iO.stream, _iO.autoPlay, (_iO.whileloading?1:0), _iO.loops||1, _iO.usePolicyFile); 1459 } else { 1460 _flash._load(_t.sID, _iO.url, !!(_iO.stream), !!(_iO.autoPlay), _iO.loops||1, !!(_iO.autoLoad), _iO.usePolicyFile); 1461 } 1462 } catch(e) { 1463 _wDS('smError', 2); 1464 _debugTS('onload', false); 1465 _catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true}); 1466 1467 } 1468 1469 } 1470 1471 return _t; 1472 1473 }; 1474 1475 /** 1476 * Unloads a sound, canceling any open HTTP requests. 1477 * 1478 * @return {SMSound} The SMSound object 1479 */ 1480 1481 this.unload = function() { 1482 1483 // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL 1484 // Flash 9/AS3: Close stream, preventing further load 1485 // HTML5: Most UAs will use empty URL 1486 1487 if (_t.readyState !== 0) { 1488 1489 _s._wD('SMSound.unload(): "' + _t.sID + '"'); 1490 1491 if (!_t.isHTML5) { 1492 if (_fV === 8) { 1493 _flash._unload(_t.sID, _emptyURL); 1494 } else { 1495 _flash._unload(_t.sID); 1496 } 1497 } else { 1498 _stop_html5_timer(); 1499 if (_t._a) { 1500 _t._a.pause(); 1501 _html5Unload(_t._a); 1502 } 1503 } 1504 1505 // reset load/status flags 1506 _resetProperties(); 1507 1508 } 1509 1510 return _t; 1511 1512 }; 1513 1514 /** 1515 * Unloads and destroys a sound. 1516 */ 1517 1518 this.destruct = function(_bFromSM) { 1519 1520 _s._wD('SMSound.destruct(): "' + _t.sID + '"'); 1521 1522 if (!_t.isHTML5) { 1523 1524 // kill sound within Flash 1525 // Disable the onfailure handler 1526 _t._iO.onfailure = null; 1527 _flash._destroySound(_t.sID); 1528 1529 } else { 1530 1531 _stop_html5_timer(); 1532 1533 if (_t._a) { 1534 _t._a.pause(); 1535 _html5Unload(_t._a); 1536 if (!_useGlobalHTML5Audio) { 1537 _remove_html5_events(); 1538 } 1539 // break obvious circular reference 1540 _t._a._t = null; 1541 _t._a = null; 1542 } 1543 1544 } 1545 1546 if (!_bFromSM) { 1547 // ensure deletion from controller 1548 _s.destroySound(_t.sID, true); 1549 1550 } 1551 1552 }; 1553 1554 /** 1555 * Begins playing a sound. 1556 * 1557 * @param {object} oOptions Optional: Sound options 1558 * @return {SMSound} The SMSound object 1559 */ 1560 1561 this.play = function(oOptions, _updatePlayState) { 1562 1563 var fN, allowMulti, a, onready; 1564 1565 // <d> 1566 fN = 'SMSound.play(): '; 1567 // </d> 1568 1569 _updatePlayState = _updatePlayState === undefined ? true : _updatePlayState; // default to true 1570 1571 if (!oOptions) { 1572 oOptions = {}; 1573 } 1574 1575 _t._iO = _mixin(oOptions, _t._iO); 1576 _t._iO = _mixin(_t._iO, _t.options); 1577 _t._iO.url = _parseURL(_t._iO.url); 1578 _t.instanceOptions = _t._iO; 1579 1580 // RTMP-only 1581 if (_t._iO.serverURL && !_t.connected) { 1582 if (!_t.getAutoPlay()) { 1583 _s._wD(fN+' Netstream not connected yet - setting autoPlay'); 1584 _t.setAutoPlay(true); 1585 } 1586 // play will be called in _onconnect() 1587 return _t; 1588 } 1589 1590 if (_html5OK(_t._iO)) { 1591 _t._setup_html5(_t._iO); 1592 _start_html5_timer(); 1593 } 1594 1595 if (_t.playState === 1 && !_t.paused) {
1596 allowMulti = _t._iO.multiShot; 1597 if (!allowMulti) { 1598 _s._wD(fN + '"' + _t.sID + '" already playing (one-shot)', 1); 1599 return _t; 1600 } else { 1601 _s._wD(fN + '"' + _t.sID + '" already playing (multi-shot)', 1); 1602 } 1603 } 1604 1605 if (!_t.loaded) { 1606 1607 if (_t.readyState === 0) { 1608 1609 _s._wD(fN + 'Attempting to load "' + _t.sID + '"', 1); 1610 1611 // try to get this sound playing ASAP 1612 if (!_t.isHTML5) { 1613 // assign directly because setAutoPlay() increments the instanceCount 1614 _t._iO.autoPlay = true; 1615 } 1616 1617 _t.load(_t._iO); 1618 1619 } else if (_t.readyState === 2) { 1620 1621 _s._wD(fN + 'Could not load "' + _t.sID + '" - exiting', 2); 1622 return _t; 1623 1624 } else { 1625 1626 _s._wD(fN + '"' + _t.sID + '" is loading - attempting to play..', 1); 1627 1628 } 1629 1630 } else { 1631 1632 _s._wD(fN + '"' + _t.sID + '"'); 1633 1634 } 1635 1636 if (!_t.isHTML5 && _fV === 9 && _t.position > 0 && _t.position === _t.duration) { 1637 // flash 9 needs a position reset if play() is called while at the end of a sound. 1638 _s._wD(fN + '"' + _t.sID + '": Sound at end, resetting to position:0'); 1639 oOptions.position = 0; 1640 } 1641 1642 /** 1643 * Streams will pause when their buffer is full if they are being loaded. 1644 * In this case paused is true, but the song hasn't started playing yet. 1645 * If we just call resume() the onplay() callback will never be called. 1646 * So only call resume() if the position is > 0. 1647 * Another reason is because options like volume won't have been applied yet. 1648 */ 1649 1650 if (_t.paused && _t.position && _t.position > 0) { 1651 1652 // https://gist.github.com/37b17df75cc4d7a90bf6 1653 _s._wD(fN + '"' + _t.sID + '" is resuming from paused state',1); 1654 _t.resume(); 1655 1656 } else { 1657 1658 _t._iO = _mixin(oOptions, _t._iO); 1659 1660 // apply from/to parameters, if they exist (and not using RTMP) 1661 if (_t._iO.from !== null && _t._iO.to !== null && _t.instanceCount === 0 && _t.playState === 0 && !_t._iO.serverURL) { 1662 1663 onready = function() { 1664 // sound "canplay" or onload() 1665 // re-apply from/to to instance options, and start playback 1666 _t._iO = _mixin(oOptions, _t._iO); 1667 _t.play(_t._iO); 1668 }; 1669 1670 // HTML5 needs to at least have "canplay" fired before seeking. 1671 if (_t.isHTML5 && !_t._html5_canplay) { 1672 1673 // this hasn't been loaded yet. load it first, and then do this again. 1674 _s._wD(fN+'Beginning load of "'+ _t.sID+'" for from/to case'); 1675 1676 _t.load({ 1677 _oncanplay: onready 1678 }); 1679 1680 return false; 1681 1682 } else if (!_t.isHTML5 && !_t.loaded && (!_t.readyState || _t.readyState !== 2)) { 1683 1684 // to be safe, preload the whole thing in Flash. 1685 1686 _s._wD(fN+'Preloading "'+ _t.sID+'" for from/to case'); 1687 1688 _t.load({ 1689 onload: onready 1690 }); 1691 1692 return false; 1693 1694 } 1695 1696 // otherwise, we're ready to go. re-apply local options, and continue 1697 1698 _t._iO = _applyFromTo(); 1699 1700 } 1701 1702 _s._wD(fN+'"'+ _t.sID+'" is starting to play'); 1703 1704 if (!_t.instanceCount || _t._iO.multiShotEvents || (!_t.isHTML5 && _fV > 8 && !_t.getAutoPlay())) { 1705 _t.instanceCount++; 1706 } 1707 1708 // if first play and onposition parameters exist, apply them now 1709 if (_t.playState === 0 && _t._iO.onposition) { 1710 _attachOnPosition(_t); 1711 } 1712 1713 _t.playState = 1; 1714 _t.paused = false; 1715 1716 _t.position = (typeof _t._iO.position !== 'undefined' && !isNaN(_t._iO.position) ? _t._iO.position : 0); 1717 1718 if (!_t.isHTML5) { 1719 _t._iO = _policyFix(_loopFix(_t._iO)); 1720 } 1721 1722 if (_t._iO.onplay && _updatePlayState) { 1723 _t._iO.onplay.apply(_t); 1724 _onplay_called = true; 1725 } 1726 1727 _t.setVolume(_t._iO.volume, true); 1728 _t.setPan(_t._iO.pan, true); 1729 1730 if (!_t.isHTML5) { 1731 1732 _flash._start(_t.sID, _t._iO.loops || 1, (_fV === 9?_t._iO.position:_t._iO.position / 1000)); 1733 1734 } else { 1735 1736 _start_html5_timer(); 1737 a = _t._setup_html5(); 1738 _t.setPosition(_t._iO.position); 1739 a.play(); 1740 1741 } 1742 1743 } 1744 1745 return _t; 1746 1747 }; 1748 1749 // just for convenience 1750 this.start = this.play; 1751 1752 /** 1753 * Stops playing a sound (and optionally, all sounds) 1754 * 1755 * @param {boolean} bAll Optional: Whether to stop all sounds 1756 * @return {SMSound} The SMSound object 1757 */ 1758 1759 this.stop = function(bAll) { 1760 1761 var _iO = _t._iO, _oP; 1762 1763 if (_t.playState === 1) { 1764 1765 _t._onbufferchange(0); 1766 _t._resetOnPosition(0); 1767 _t.paused = false;
1768 1769 if (!_t.isHTML5) { 1770 _t.playState = 0; 1771 } 1772 1773 // remove onPosition listeners, if any 1774 _detachOnPosition(); 1775 1776 // and "to" position, if set 1777 if (_iO.to) { 1778 _t.clearOnPosition(_iO.to); 1779 } 1780 1781 if (!_t.isHTML5) { 1782 1783 _flash._stop(_t.sID, bAll); 1784 1785 // hack for netStream: just unload 1786 if (_iO.serverURL) { 1787 _t.unload(); 1788 } 1789 1790 } else { 1791 1792 if (_t._a) { 1793 1794 _oP = _t.position; 1795 1796 // act like Flash, though 1797 _t.setPosition(0); 1798 1799 // hack: reflect old position for onstop() (also like Flash) 1800 _t.position = _oP; 1801 1802 // html5 has no stop() 1803 // NOTE: pausing means iOS requires interaction to resume. 1804 _t._a.pause(); 1805 1806 _t.playState = 0; 1807 1808 // and update UI 1809 _t._onTimer(); 1810 1811 _stop_html5_timer(); 1812 1813 } 1814 1815 } 1816 1817 _t.instanceCount = 0; 1818 _t._iO = {}; 1819 1820 if (_iO.onstop) { 1821 _iO.onstop.apply(_t); 1822 } 1823 1824 } 1825 1826 return _t; 1827 1828 }; 1829 1830 /** 1831 * Undocumented/internal: Sets autoPlay for RTMP. 1832 * 1833 * @param {boolean} autoPlay state 1834 */ 1835 1836 this.setAutoPlay = function(autoPlay) { 1837 1838 _s._wD('sound '+_t.sID+' turned autoplay ' + (autoPlay ? 'on' : 'off')); 1839 _t._iO.autoPlay = autoPlay; 1840 1841 if (!_t.isHTML5) { 1842 _flash._setAutoPlay(_t.sID, autoPlay); 1843 if (autoPlay) { 1844 // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP) 1845 if (!_t.instanceCount && _t.readyState === 1) { 1846 _t.instanceCount++; 1847 _s._wD('sound '+_t.sID+' incremented instance count to '+_t.instanceCount); 1848 } 1849 } 1850 } 1851 1852 }; 1853 1854 /** 1855 * Undocumented/internal: Returns the autoPlay boolean. 1856 * 1857 * @return {boolean} The current autoPlay value 1858 */ 1859 1860 this.getAutoPlay = function() { 1861 1862 return _t._iO.autoPlay; 1863 1864 }; 1865 1866 /** 1867 * Sets the position of a sound. 1868 * 1869 * @param {number} nMsecOffset Position (milliseconds) 1870 * @return {SMSound} The SMSound object 1871 */ 1872 1873 this.setPosition = function(nMsecOffset) { 1874 1875 if (nMsecOffset === undefined) { 1876 nMsecOffset = 0; 1877 } 1878 1879 var original_pos, 1880 position, position1K, 1881 // Use the duration from the instance options, if we don't have a track duration yet. 1882 // position >= 0 and <= current available (loaded) duration 1883 offset = (_t.isHTML5 ? Math.max(nMsecOffset,0) : Math.min(_t.duration || _t._iO.duration, Math.max(nMsecOffset, 0))); 1884 1885 original_pos = _t.position; 1886 _t.position = offset; 1887 position1K = _t.position/1000; 1888 _t._resetOnPosition(_t.position); 1889 _t._iO.position = offset; 1890 1891 if (!_t.isHTML5) { 1892 1893 position = (_fV === 9 ? _t.position : position1K); 1894 if (_t.readyState && _t.readyState !== 2) { 1895 // if paused or not playing, will not resume (by playing) 1896 _flash._setPosition(_t.sID, position, (_t.paused || !_t.playState)); 1897 } 1898 1899 } else if (_t._a) { 1900 1901 // Set the position in the canplay handler if the sound is not ready yet 1902 if (_t._html5_canplay) { 1903 if (_t._a.currentTime !== position1K) { 1904 /** 1905 * DOM/JS errors/exceptions to watch out for: 1906 * if seek is beyond (loaded?) position, "DOM exception 11" 1907 * "INDEX_SIZE_ERR": DOM exception 1 1908 */ 1909 _s._wD('setPosition('+position1K+'): setting position'); 1910 try { 1911 _t._a.currentTime = position1K; 1912 if (_t.playState === 0 || _t.paused) { 1913 // allow seek without auto-play/resume 1914 _t._a.pause(); 1915 } 1916 } catch(e) { 1917 _s._wD('setPosition('+position1K+'): setting position failed: '+e.message, 2); 1918 } 1919 } 1920 } else { 1921 _s._wD('setPosition('+position1K+'): delaying, sound not ready'); 1922 } 1923 1924 } 1925 1926 if (_t.isHTML5) { 1927 if (_t.paused) { 1928 // if paused, refresh UI right away 1929 // force update 1930 _t._onTimer(true); 1931 } 1932 } 1933 1934 return _t; 1935 1936 }; 1937 1938 /** 1939 * Pauses sound playback. 1940 * 1941 * @return {SMSound} The SMSound object 1942 */ 1943 1944 this.pause = function(_bCallFlash) { 1945 1946 if (_t.paused || (_t.playState === 0 && _t.readyState !== 1)) { 1947 return _t; 1948 } 1949 1950 _s._wD('SMSound.pause()'); 1951 _t.paused = true; 1952 1953 if (!_t.isHTML5) { 1954 if (_bCallFlash || _bCallFlash === undefined) { 1955 _flash._pause(_t.sID); 1956 } 1957 } else { 1958 _t._setup_html5().pause(); 1959 _stop_html5_timer(); 1960 } 1961 1962 if (_t._iO.onpause) { 1963 _t._iO.onpause.apply(_t); 1964 } 1965 1966 return _t; 1967 1968 }; 1969 1970 /** 1971 * Resumes sound playback. 1972 * 1973 * @return {SMSound} The SMSound object 1974 */ 1975 1976 /** 1977 * When auto-loaded streams pause on buffer full they have a playState of 0. 1978 * We need to make sure that the playState is set to 1 when these streams "resume". 1979 * When a paused stream is resumed, we need to trigger the onplay() callback if it 1980 * hasn't been called already. In this case since the sound is being played for the 1981 * first time, I think it's more appropriate to call onplay() rather than onresume(). 1982 */ 1983 1984 this.resume = function() { 1985 1986 var _iO = _t._iO; 1987 1988 if (!_t.paused) { 1989 return _t; 1990 } 1991 1992 _s._wD('SMSound.resume()'); 1993 _t.paused = false;
1994 _t.playState = 1; 1995 1996 if (!_t.isHTML5) { 1997 if (_iO.isMovieStar && !_iO.serverURL) { 1998 // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition. 1999 _t.setPosition(_t.position); 2000 } 2001 // flash method is toggle-based (pause/resume) 2002 _flash._pause(_t.sID); 2003 } else { 2004 _t._setup_html5().play(); 2005 _start_html5_timer(); 2006 } 2007 2008 if (_onplay_called && _iO.onplay) { 2009 _iO.onplay.apply(_t); 2010 _onplay_called = true; 2011 } else if (_iO.onresume) { 2012 _iO.onresume.apply(_t); 2013 } 2014 2015 return _t; 2016 2017 }; 2018 2019 /** 2020 * Toggles sound playback. 2021 * 2022 * @return {SMSound} The SMSound object 2023 */ 2024 2025 this.togglePause = function() { 2026 2027 _s._wD('SMSound.togglePause()'); 2028 2029 if (_t.playState === 0) { 2030 _t.play({ 2031 position: (_fV === 9 && !_t.isHTML5 ? _t.position : _t.position / 1000) 2032 }); 2033 return _t; 2034 } 2035 2036 if (_t.paused) { 2037 _t.resume(); 2038 } else { 2039 _t.pause(); 2040 } 2041 2042 return _t; 2043 2044 }; 2045 2046 /** 2047 * Sets the panning (L-R) effect. 2048 * 2049 * @param {number} nPan The pan value (-100 to 100) 2050 * @return {SMSound} The SMSound object 2051 */ 2052 2053 this.setPan = function(nPan, bInstanceOnly) { 2054 2055 if (typeof nPan === 'undefined') { 2056 nPan = 0; 2057 } 2058 2059 if (typeof bInstanceOnly === 'undefined') { 2060 bInstanceOnly = false; 2061 } 2062 2063 if (!_t.isHTML5) { 2064 _flash._setPan(_t.sID, nPan); 2065 } // else { no HTML5 pan? } 2066 2067 _t._iO.pan = nPan; 2068 2069 if (!bInstanceOnly) { 2070 _t.pan = nPan; 2071 _t.options.pan = nPan; 2072 } 2073 2074 return _t; 2075 2076 }; 2077 2078 /** 2079 * Sets the volume. 2080 * 2081 * @param {number} nVol The volume value (0 to 100) 2082 * @return {SMSound} The SMSound object 2083 */ 2084 2085 this.setVolume = function(nVol, _bInstanceOnly) { 2086 2087 /** 2088 * Note: Setting volume has no effect on iOS "special snowflake" devices. 2089 * Hardware volume control overrides software, and volume 2090 * will always return 1 per Apple docs. (iOS 4 + 5.) 2091 * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html 2092 */ 2093 2094 if (typeof nVol === 'undefined') { 2095 nVol = 100; 2096 } 2097 2098 if (typeof _bInstanceOnly === 'undefined') { 2099 _bInstanceOnly = false; 2100 } 2101 2102 if (!_t.isHTML5) { 2103 _flash._setVolume(_t.sID, (_s.muted && !_t.muted) || _t.muted?0:nVol); 2104 } else if (_t._a) { 2105 // valid range: 0-1 2106 _t._a.volume = Math.max(0, Math.min(1, nVol/100)); 2107 } 2108 2109 _t._iO.volume = nVol; 2110 2111 if (!_bInstanceOnly) { 2112 _t.volume = nVol; 2113 _t.options.volume = nVol; 2114 } 2115 2116 return _t; 2117 2118 }; 2119 2120 /** 2121 * Mutes the sound. 2122 * 2123 * @return {SMSound} The SMSound object 2124 */ 2125 2126 this.mute = function() { 2127 2128 _t.muted = true; 2129 2130 if (!_t.isHTML5) { 2131 _flash._setVolume(_t.sID, 0); 2132 } else if (_t._a) { 2133 _t._a.muted = true; 2134 } 2135 2136 return _t; 2137 2138 }; 2139 2140 /** 2141 * Unmutes the sound. 2142 * 2143 * @return {SMSound} The SMSound object 2144 */ 2145 2146 this.unmute = function() { 2147 2148 _t.muted = false; 2149 var hasIO = typeof _t._iO.volume !== 'undefined'; 2150 2151 if (!_t.isHTML5) { 2152 _flash._setVolume(_t.sID, hasIO?_t._iO.volume:_t.options.volume); 2153 } else if (_t._a) { 2154 _t._a.muted = false; 2155 } 2156 2157 return _t; 2158 2159 }; 2160 2161 /** 2162 * Toggles the muted state of a sound. 2163 * 2164 * @return {SMSound} The SMSound object 2165 */ 2166 2167 this.toggleMute = function() { 2168 2169 return (_t.muted?_t.unmute():_t.mute()); 2170 2171 }; 2172 2173 /** 2174 * Registers a callback to be fired when a sound reaches a given position during playback. 2175 * 2176 * @param {number} nPosition The position to watch for 2177 * @param {function} oMethod The relevant callback to fire 2178 * @param {object} oScope Optional: The scope to apply the callback to 2179 * @return {SMSound} The SMSound object 2180 */ 2181 2182 this.onPosition = function(nPosition, oMethod, oScope) { 2183 2184 // TODO: basic dupe checking? 2185 2186 _onPositionItems.push({ 2187 position: nPosition, 2188 method: oMethod, 2189 scope: (typeof oScope !== 'undefined' ? oScope : _t), 2190 fired: false 2191 }); 2192 2193 return _t; 2194 2195 }; 2196
2197 // legacy/backwards-compability: lower-case method name 2198 this.onposition = this.onPosition; 2199 2200 /** 2201 * Removes registered callback(s) from a sound, by position and/or callback. 2202 * 2203 * @param {number} nPosition The position to clear callback(s) for 2204 * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position 2205 * @return {SMSound} The SMSound object 2206 */ 2207 2208 this.clearOnPosition = function(nPosition, oMethod) { 2209 2210 var i; 2211 2212 nPosition = parseInt(nPosition, 10); 2213 2214 if (isNaN(nPosition)) { 2215 // safety check 2216 return false; 2217 } 2218 2219 for (i=0; i < _onPositionItems.length; i++) { 2220 2221 if (nPosition === _onPositionItems[i].position) { 2222 // remove this item if no method was specified, or, if the method matches 2223 if (!oMethod || (oMethod === _onPositionItems[i].method)) { 2224 if (_onPositionItems[i].fired) { 2225 // decrement "fired" counter, too 2226 _onPositionFired--; 2227 } 2228 _onPositionItems.splice(i, 1); 2229 } 2230 } 2231 2232 } 2233 2234 }; 2235 2236 this._processOnPosition = function() { 2237 2238 var i, item, j = _onPositionItems.length; 2239 2240 if (!j || !_t.playState || _onPositionFired >= j) { 2241 return false; 2242 } 2243 2244 for (i=j; i--;) { 2245 item = _onPositionItems[i]; 2246 if (!item.fired && _t.position >= item.position) { 2247 item.fired = true; 2248 _onPositionFired++; 2249 item.method.apply(item.scope, [item.position]); 2250 } 2251 } 2252 2253 return true; 2254 2255 }; 2256 2257 this._resetOnPosition = function(nPosition) { 2258 2259 // reset "fired" for items interested in this position 2260 var i, item, j = _onPositionItems.length; 2261 2262 if (!j) { 2263 return false; 2264 } 2265 2266 for (i=j; i--;) { 2267 item = _onPositionItems[i]; 2268 if (item.fired && nPosition <= item.position) { 2269 item.fired = false; 2270 _onPositionFired--; 2271 } 2272 } 2273 2274 return true; 2275 2276 }; 2277 2278 /** 2279 * SMSound() private internals 2280 * -------------------------------- 2281 */ 2282 2283 _applyFromTo = function() { 2284 2285 var _iO = _t._iO, 2286 f = _iO.from, 2287 t = _iO.to, 2288 start, end; 2289 2290 end = function() { 2291 2292 // end has been reached. 2293 _s._wD(_t.sID + ': "to" time of ' + t + ' reached.'); 2294 2295 // detach listener 2296 _t.clearOnPosition(t, end); 2297 2298 // stop should clear this, too 2299 _t.stop(); 2300 2301 }; 2302 2303 start = function() { 2304 2305 _s._wD(_t.sID + ': playing "from" ' + f); 2306 2307 // add listener for end 2308 if (t !== null && !isNaN(t)) { 2309 _t.onPosition(t, end); 2310 } 2311 2312 }; 2313 2314 if (f !== null && !isNaN(f)) { 2315 2316 // apply to instance options, guaranteeing correct start position. 2317 _iO.position = f; 2318 2319 // multiShot timing can't be tracked, so prevent that. 2320 _iO.multiShot = false; 2321 2322 start(); 2323 2324 } 2325 2326 // return updated instanceOptions including starting position 2327 return _iO; 2328 2329 }; 2330 2331 _attachOnPosition = function() { 2332 2333 var op = _t._iO.onposition; 2334 2335 // attach onposition things, if any, now. 2336 2337 if (op) { 2338 2339 var item; 2340 2341 for (item in op) { 2342 if (op.hasOwnProperty(item)) { 2343 _t.onPosition(parseInt(item, 10), op[item]); 2344 } 2345 } 2346 2347 } 2348 2349 }; 2350 2351 _detachOnPosition = function() { 2352 2353 var op = _t._iO.onposition; 2354 2355 // detach any onposition()-style listeners. 2356 2357 if (op) { 2358 2359 var item; 2360 2361 for (item in op) { 2362 if (op.hasOwnProperty(item)) { 2363 _t.clearOnPosition(parseInt(item, 10)); 2364 } 2365 } 2366 2367 } 2368 2369 }; 2370 2371 _start_html5_timer = function() { 2372 2373 if (_t.isHTML5) { 2374 _startTimer(_t); 2375 } 2376 2377 }; 2378 2379 _stop_html5_timer = function() { 2380 2381 if (_t.isHTML5) { 2382 _stopTimer(_t); 2383 } 2384 2385 }; 2386 2387 _resetProperties = function() { 2388 2389 _onPositionItems = []; 2390 _onPositionFired = 0; 2391 _onplay_called = false; 2392 2393 _t._hasTimer = null; 2394 _t._a = null; 2395 _t._html5_canplay = false;
2396 _t.bytesLoaded = null; 2397 _t.bytesTotal = null; 2398 _t.duration = (_t._iO && _t._iO.duration ? _t._iO.duration : null); 2399 _t.durationEstimate = null; 2400 2401 // legacy: 1D array 2402 _t.eqData = []; 2403 2404 _t.eqData.left = []; 2405 _t.eqData.right = []; 2406 2407 _t.failures = 0; 2408 _t.isBuffering = false; 2409 _t.instanceOptions = {}; 2410 _t.instanceCount = 0; 2411 _t.loaded = false; 2412 _t.metadata = {}; 2413 2414 // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success 2415 _t.readyState = 0; 2416 2417 _t.muted = false; 2418 _t.paused = false; 2419 2420 _t.peakData = { 2421 left: 0, 2422 right: 0 2423 }; 2424 2425 _t.waveformData = { 2426 left: [], 2427 right: [] 2428 }; 2429 2430 _t.playState = 0; 2431 _t.position = null; 2432 2433 }; 2434 2435 _resetProperties(); 2436 2437 /** 2438 * Pseudo-private SMSound internals 2439 * -------------------------------- 2440 */ 2441 2442 this._onTimer = function(bForce) { 2443 2444 /** 2445 * HTML5-only _whileplaying() etc. 2446 * called from both HTML5 native events, and polling/interval-based timers 2447 * mimics flash and fires only when time/duration change, so as to be polling-friendly 2448 */ 2449 2450 var duration, isNew = false, time, x = {}; 2451 2452 if (_t._hasTimer || bForce) { 2453 2454 // TODO: May not need to track readyState (1 = loading) 2455 2456 if (_t._a && (bForce || ((_t.playState > 0 || _t.readyState === 1) && !_t.paused))) { 2457 2458 duration = _t._get_html5_duration(); 2459 2460 if (duration !== _lastHTML5State.duration) { 2461 2462 _lastHTML5State.duration = duration; 2463 _t.duration = duration; 2464 isNew = true; 2465 2466 } 2467 2468 // TODO: investigate why this goes wack if not set/re-set each time. 2469 _t.durationEstimate = _t.duration; 2470 2471 time = (_t._a.currentTime * 1000 || 0); 2472 2473 if (time !== _lastHTML5State.time) { 2474 2475 _lastHTML5State.time = time; 2476 isNew = true; 2477 2478 } 2479 2480 if (isNew || bForce) { 2481 2482 _t._whileplaying(time,x,x,x,x); 2483 2484 } 2485 2486 return isNew; 2487 2488 } else { 2489 2490 // _s._wD('_onTimer: Warn for "'+_t.sID+'": '+(!_t._a?'Could not find element. ':'')+(_t.playState === 0?'playState bad, 0?':'playState = '+_t.playState+', OK')); 2491 2492 return false; 2493 2494 } 2495 2496 } 2497 2498 }; 2499 2500 this._get_html5_duration = function() { 2501 2502 var _iO = _t._iO, 2503 d = (_t._a ? _t._a.duration*1000 : (_iO ? _iO.duration : undefined)), 2504 result = (d && !isNaN(d) && d !== Infinity ? d : (_iO ? _iO.duration : null)); 2505 2506 return result; 2507 2508 }; 2509 2510 this._setup_html5 = function(oOptions) { 2511 2512 var _iO = _mixin(_t._iO, oOptions), d = decodeURI, 2513 _a = _useGlobalHTML5Audio ? _s._global_a : _t._a, 2514 _dURL = d(_iO.url), 2515 _oldIO = (_a && _a._t ? _a._t.instanceOptions : null); 2516 2517 if (_a) { 2518 2519 if (_a._t) { 2520 2521 if (!_useGlobalHTML5Audio && _dURL === d(_lastURL)) { 2522 // same url, ignore request 2523 return _a; 2524 } else if (_useGlobalHTML5Audio && _oldIO.url === _iO.url && (!_lastURL || (_lastURL === _oldIO.url))) { 2525 // iOS-type reuse case 2526 return _a; 2527 } 2528 2529 } 2530 2531 _s._wD('setting new URL on existing object: ' + _dURL + (_lastURL ? ', old URL: ' + _lastURL : '')); 2532 2533 /** 2534 * "First things first, I, Poppa.." (reset the previous state of the old sound, if playing) 2535 * Fixes case with devices that can only play one sound at a time 2536 * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state 2537 */ 2538 2539 if (_useGlobalHTML5Audio && _a._t && _a._t.playState && _iO.url !== _oldIO.url) { 2540 _a._t.stop(); 2541 } 2542 2543 // new URL, so reset load/playstate and so on 2544 _resetProperties(); 2545 2546 _a.src = _iO.url; 2547 _t.url = _iO.url; 2548 _lastURL = _iO.url; 2549 _a._called_load = false; 2550 2551 } else { 2552 2553 _s._wD('creating HTML5 Audio() element with URL: '+_dURL); 2554 _a = new Audio(_iO.url); 2555 2556 _a._called_load = false;
2557 2558 // android (seen in 2.3/Honeycomb) sometimes fails first .load() -> .play(), results in playback failure and ended() events? 2559 if (_is_android) { 2560 _a._called_load = true; 2561 } 2562 2563 if (_useGlobalHTML5Audio) { 2564 _s._global_a = _a; 2565 } 2566 2567 } 2568 2569 _t.isHTML5 = true; 2570 2571 // store a ref on the track 2572 _t._a = _a; 2573 2574 // store a ref on the audio 2575 _a._t = _t; 2576 2577 _add_html5_events(); 2578 _a.loop = (_iO.loops>1?'loop':''); 2579 2580 if (_iO.autoLoad || _iO.autoPlay) { 2581 2582 _t.load(); 2583 2584 } else { 2585 2586 // early HTML5 implementation (non-standard) 2587 _a.autobuffer = false; 2588 2589 // standard 2590 _a.preload = 'none'; 2591 2592 } 2593 2594 // boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop 2595 _a.loop = (_iO.loops > 1 ? 'loop' : ''); 2596 2597 return _a; 2598 2599 }; 2600 2601 _add_html5_events = function() { 2602 2603 if (_t._a._added_events) { 2604 return false; 2605 } 2606 2607 var f; 2608 2609 function add(oEvt, oFn, bCapture) { 2610 return _t._a ? _t._a.addEventListener(oEvt, oFn, bCapture||false) : null; 2611 } 2612 2613 _s._wD(_h5+'adding event listeners: '+_t.sID); 2614 _t._a._added_events = true; 2615 2616 for (f in _html5_events) { 2617 if (_html5_events.hasOwnProperty(f)) { 2618 add(f, _html5_events[f]); 2619 } 2620 } 2621 2622 return true; 2623 2624 }; 2625 2626 _remove_html5_events = function() { 2627 2628 // Remove event listeners 2629 2630 var f; 2631 2632 function remove(oEvt, oFn, bCapture) { 2633 return (_t._a ? _t._a.removeEventListener(oEvt, oFn, bCapture||false) : null); 2634 } 2635 2636 _s._wD(_h5+'removing event listeners: '+_t.sID); 2637 _t._a._added_events = false; 2638 2639 for (f in _html5_events) { 2640 if (_html5_events.hasOwnProperty(f)) { 2641 remove(f, _html5_events[f]); 2642 } 2643 } 2644 2645 }; 2646 2647 /** 2648 * Pseudo-private event internals 2649 * ------------------------------ 2650 */ 2651 2652 this._onload = function(nSuccess) { 2653 2654 2655 var fN, loadOK = !!(nSuccess); 2656 _s._wD(fN + '"' + _t.sID + '"' + (loadOK?' loaded.':' failed to load? - ' + _t.url), (loadOK?1:2)); 2657 2658 // <d> 2659 fN = 'SMSound._onload(): '; 2660 if (!loadOK && !_t.isHTML5) { 2661 if (_s.sandbox.noRemote === true) { 2662 _s._wD(fN + _str('noNet'), 1); 2663 } 2664 if (_s.sandbox.noLocal === true) { 2665 _s._wD(fN + _str('noLocal'), 1); 2666 } 2667 } 2668 // </d> 2669 2670 _t.loaded = loadOK; 2671 _t.readyState = loadOK?3:2; 2672 _t._onbufferchange(0); 2673 2674 if (_t._iO.onload) { 2675 _t._iO.onload.apply(_t, [loadOK]); 2676 } 2677 2678 return true; 2679 2680 }; 2681 2682 this._onbufferchange = function(nIsBuffering) { 2683 2684 if (_t.playState === 0) { 2685 // ignore if not playing 2686 return false; 2687 } 2688 2689 if ((nIsBuffering && _t.isBuffering) || (!nIsBuffering && !_t.isBuffering)) { 2690 return false; 2691 } 2692 2693 _t.isBuffering = (nIsBuffering === 1); 2694 if (_t._iO.onbufferchange) { 2695 _s._wD('SMSound._onbufferchange(): ' + nIsBuffering); 2696 _t._iO.onbufferchange.apply(_t); 2697 } 2698 2699 return true; 2700 2701 }; 2702 2703 /** 2704 * Notify Mobile Safari that user action is required 2705 * to continue playing / loading the audio file. 2706 */ 2707 2708 this._onsuspend = function() { 2709 2710 if (_t._iO.onsuspend) { 2711 _s._wD('SMSound._onsuspend()'); 2712 _t._iO.onsuspend.apply(_t); 2713 } 2714 2715 return true; 2716 2717 }; 2718 2719 /** 2720 * flash 9/movieStar + RTMP-only method, should fire only once at most 2721 * at this point we just recreate failed sounds rather than trying to reconnect 2722 */ 2723 2724 this._onfailure = function(msg, level, code) { 2725 2726 _t.failures++; 2727 _s._wD('SMSound._onfailure(): "'+_t.sID+'" count '+_t.failures); 2728 2729 if (_t._iO.onfailure && _t.failures === 1) { 2730 _t._iO.onfailure(_t, msg, level, code); 2731 } else { 2732 _s._wD('SMSound._onfailure(): ignoring'); 2733 } 2734 2735 }; 2736 2737 this._onfinish = function() { 2738 2739 // store local copy before it gets trashed.. 2740 var _io_onfinish = _t._iO.onfinish; 2741 2742 _t._onbufferchange(0); 2743 _t._resetOnPosition(0); 2744 2745 // reset some state items 2746 if (_t.instanceCount) { 2747 2748 _t.instanceCount--; 2749 2750 if (!_t.instanceCount) { 2751 2752 // remove onPosition listeners, if any 2753 _detachOnPosition(); 2754 2755 // reset instance options 2756 _t.playState = 0; 2757 _t.paused = false;
2758 _t.instanceCount = 0; 2759 _t.instanceOptions = {}; 2760 _t._iO = {}; 2761 _stop_html5_timer(); 2762 2763 } 2764 2765 if (!_t.instanceCount || _t._iO.multiShotEvents) { 2766 // fire onfinish for last, or every instance 2767 if (_io_onfinish) { 2768 _s._wD('SMSound._onfinish(): "' + _t.sID + '"'); 2769 _io_onfinish.apply(_t); 2770 } 2771 } 2772 2773 } 2774 2775 }; 2776 2777 this._whileloading = function(nBytesLoaded, nBytesTotal, nDuration, nBufferLength) { 2778 2779 var _iO = _t._iO; 2780 2781 _t.bytesLoaded = nBytesLoaded; 2782 _t.bytesTotal = nBytesTotal; 2783 _t.duration = Math.floor(nDuration); 2784 _t.bufferLength = nBufferLength; 2785 2786 if (!_iO.isMovieStar) { 2787 2788 if (_iO.duration) { 2789 // use options, if specified and larger 2790 _t.durationEstimate = (_t.duration > _iO.duration) ? _t.duration : _iO.duration; 2791 } else { 2792 _t.durationEstimate = parseInt((_t.bytesTotal / _t.bytesLoaded) * _t.duration, 10); 2793 2794 } 2795 2796 if (_t.durationEstimate === undefined) { 2797 _t.durationEstimate = _t.duration; 2798 } 2799 2800 if (_t.readyState !== 3 && _iO.whileloading) { 2801 _iO.whileloading.apply(_t); 2802 } 2803 2804 } else { 2805 2806 _t.durationEstimate = _t.duration; 2807 if (_t.readyState !== 3 && _iO.whileloading) { 2808 _iO.whileloading.apply(_t); 2809 } 2810 2811 } 2812 2813 }; 2814 2815 this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) { 2816 2817 var _iO = _t._iO; 2818 2819 if (isNaN(nPosition) || nPosition === null) { 2820 // flash safety net 2821 return false; 2822 } 2823 2824 _t.position = nPosition; 2825 _t._processOnPosition(); 2826 2827 if (!_t.isHTML5 && _fV > 8) { 2828 2829 if (_iO.usePeakData && typeof oPeakData !== 'undefined' && oPeakData) { 2830 _t.peakData = { 2831 left: oPeakData.leftPeak, 2832 right: oPeakData.rightPeak 2833 }; 2834 } 2835 2836 if (_iO.useWaveformData && typeof oWaveformDataLeft !== 'undefined' && oWaveformDataLeft) { 2837 _t.waveformData = { 2838 left: oWaveformDataLeft.split(','), 2839 right: oWaveformDataRight.split(',') 2840 }; 2841 } 2842 2843 if (_iO.useEQData) { 2844 if (typeof oEQData !== 'undefined' && oEQData && oEQData.leftEQ) { 2845 var eqLeft = oEQData.leftEQ.split(','); 2846 _t.eqData = eqLeft; 2847 _t.eqData.left = eqLeft; 2848 if (typeof oEQData.rightEQ !== 'undefined' && oEQData.rightEQ) { 2849 _t.eqData.right = oEQData.rightEQ.split(','); 2850 } 2851 } 2852 } 2853 2854 } 2855 2856 if (_t.playState === 1) { 2857 2858 // special case/hack: ensure buffering is false if loading from cache (and not yet started) 2859 if (!_t.isHTML5 && _fV === 8 && !_t.position && _t.isBuffering) { 2860 _t._onbufferchange(0); 2861 } 2862 2863 if (_iO.whileplaying) { 2864 // flash may call after actual finish 2865 _iO.whileplaying.apply(_t); 2866 } 2867 2868 } 2869 2870 return true; 2871 2872 }; 2873 2874 this._onmetadata = function(oMDProps, oMDData) { 2875 2876 /** 2877 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature 2878 * RTMP may include song title, MovieStar content may include encoding info 2879 * 2880 * @param {array} oMDProps (names) 2881 * @param {array} oMDData (values) 2882 */ 2883 2884 _s._wD('SMSound._onmetadata(): "' + this.sID + '" metadata received.'); 2885 2886 var oData = {}, i, j; 2887 2888 for (i = 0, j = oMDProps.length; i < j; i++) { 2889 oData[oMDProps[i]] = oMDData[i]; 2890 } 2891 _t.metadata = oData; 2892 2893 if (_t._iO.onmetadata) { 2894 _t._iO.onmetadata.apply(_t); 2895 } 2896 2897 }; 2898 2899 this._onid3 = function(oID3Props, oID3Data) { 2900 2901 /** 2902 * internal: flash 8 + flash 9 ID3 feature 2903 * may include artist, song title etc. 2904 * 2905 * @param {array} oID3Props (names) 2906 * @param {array} oID3Data (values) 2907 */ 2908 2909 _s._wD('SMSound._onid3(): "' + this.sID + '" ID3 data received.'); 2910 2911 var oData = [], i, j; 2912 2913 for (i = 0, j = oID3Props.length; i < j; i++) { 2914 oData[oID3Props[i]] = oID3Data[i]; 2915 } 2916 _t.id3 = _mixin(_t.id3, oData); 2917 2918 if (_t._iO.onid3) { 2919 _t._iO.onid3.apply(_t); 2920 } 2921 2922 }; 2923 2924 // flash/RTMP-only 2925 2926 this._onconnect = function(bSuccess) { 2927 2928 bSuccess = (bSuccess === 1); 2929 _s._wD('SMSound._onconnect(): "'+_t.sID+'"'+(bSuccess?' connected.':' failed to connect? - '+_t.url), (bSuccess?1:2)); 2930 _t.connected = bSuccess; 2931 2932 if (bSuccess) { 2933 2934 _t.failures = 0; 2935 2936 if (_idCheck(_t.sID)) { 2937 if (_t.getAutoPlay()) { 2938 // only update the play state if auto playing 2939 _t.play(undefined, _t.getAutoPlay()); 2940 } else if (_t._iO.autoLoad) { 2941 _t.load(); 2942 } 2943 } 2944 2945 if (_t._iO.onconnect) { 2946 _t._iO.onconnect.apply(_t, [bSuccess]); 2947 } 2948 2949 } 2950 2951 }; 2952 2953 this._ondataerror = function(sError) { 2954 2955 // flash 9 wave/eq data handler 2956 // hack: called at start, and end from flash at/after onfinish() 2957 if (_t.playState > 0) { 2958 _s._wD('SMSound._ondataerror(): ' + sError); 2959 if (_t._iO.ondataerror) { 2960 _t._iO.ondataerror.apply(_t); 2961 } 2962 } 2963 2964 }; 2965 2966 }; // SMSound() 2967 2968 /** 2969 * Private SoundManager internals 2970 * ------------------------------ 2971 */ 2972 2973 _getDocument = function() { 2974 2975 return (_doc.body || _doc._docElement || _doc.getElementsByTagName('div')[0]); 2976 2977 }; 2978 2979 _id = function(sID) { 2980 2981 return _doc.getElementById(sID); 2982 2983 }; 2984 2985 _mixin = function(oMain, oAdd) { 2986 2987 // non-destructive merge 2988 var o1 = {}, i, o2, o; 2989 2990 // clone c1 2991 for (i in oMain) { 2992 if (oMain.hasOwnProperty(i)) { 2993 o1[i] = oMain[i]; 2994 } 2995 } 2996 2997 o2 = (typeof oAdd === 'undefined'?_s.defaultOptions:oAdd); 2998 for (o in o2) { 2999 if (o2.hasOwnProperty(o) && typeof o1[o] === 'undefined') { 3000 o1[o] = o2[o]; 3001 } 3002 } 3003 return o1; 3004 3005 }; 3006 3007 _event = (function() { 3008 3009 var old = (_win.attachEvent), 3010 evt = { 3011 add: (old?'attachEvent':'addEventListener'), 3012 remove: (old?'detachEvent':'removeEventListener') 3013 }; 3014 3015 function getArgs(oArgs) { 3016 3017 var args = _slice.call(oArgs), len = args.length; 3018 3019 if (old) { 3020 // prefix 3021 args[1] = 'on' + args[1]; 3022 if (len > 3) { 3023 // no capture 3024 args.pop(); 3025 } 3026 } else if (len === 3) { 3027 args.push(false); 3028 } 3029 3030 return args; 3031 3032 } 3033 3034 function apply(args, sType) { 3035 3036 var element = args.shift(), 3037 method = [evt[sType]]; 3038 3039 if (old) { 3040 element[method](args[0], args[1]); 3041 } else { 3042 element[method].apply(element, args); 3043 } 3044 3045 } 3046 3047 function add() { 3048 3049 apply(getArgs(arguments), 'add'); 3050 3051 } 3052 3053 function remove() { 3054 3055 apply(getArgs(arguments), 'remove'); 3056 3057 } 3058 3059 return { 3060 'add': add, 3061 'remove': remove 3062 }; 3063 3064 }()); 3065 3066 /** 3067 * Internal HTML5 event handling 3068 * ----------------------------- 3069 */ 3070 3071 function _html5_event(oFn) { 3072 3073 // wrap html5 event handlers so we don't call them on destroyed sounds 3074 3075 return function(e) { 3076 3077 var t = this._t; 3078 3079 if (!t || !t._a) { 3080 // <d> 3081 if (t && t.sID) { 3082 _s._wD(_h5+'ignoring '+e.type+': '+t.sID); 3083 } else { 3084 _s._wD(_h5+'ignoring '+e.type); 3085 } 3086 // </d> 3087 return null; 3088 } else { 3089 return oFn.call(this, e); 3090 } 3091 3092 }; 3093 3094 } 3095 3096 _html5_events = { 3097 3098 // HTML5 event-name-to-handler map 3099 3100 abort: _html5_event(function(e) { 3101 3102 _s._wD(_h5+'abort: '+this._t.sID); 3103 3104 }), 3105 3106 // enough has loaded to play 3107 3108 canplay: _html5_event(function(e) { 3109 3110 var t = this._t; 3111 3112 if (t._html5_canplay) { 3113 // this event has already fired. ignore. 3114 return true; 3115 } 3116 3117 t._html5_canplay = true; 3118 _s._wD(_h5+'canplay: '+t.sID+', '+t.url); 3119 t._onbufferchange(0); 3120 var position1K = (!isNaN(t.position)?t.position/1000:null); 3121 3122 // set the position if position was set before the sound loaded 3123 if (t.position && this.currentTime !== position1K) { 3124 _s._wD(_h5+'canplay: setting position to '+position1K); 3125 try { 3126 this.currentTime = position1K; 3127 } catch(ee) { 3128 _s._wD(_h5+'setting position failed: '+ee.message, 2); 3129 } 3130 } 3131 3132 // hack for HTML5 from/to case 3133 if (t._iO._oncanplay) { 3134 t._iO._oncanplay(); 3135 } 3136 3137 }), 3138 3139 load: _html5_event(function(e) { 3140 3141 var t = this._t; 3142 3143 if (!t.loaded) { 3144 t._onbufferchange(0); 3145 // should be 1, and the same 3146 t._whileloading(t.bytesTotal, t.bytesTotal, t._get_html5_duration()); 3147 t._onload(true); 3148 } 3149 3150 }), 3151 3152 emptied: _html5_event(function(e) { 3153 3154 _s._wD(_h5+'emptied: '+this._t.sID); 3155 3156 }), 3157 3158 ended: _html5_event(function(e) { 3159 3160 var t = this._t; 3161 3162 _s._wD(_h5+'ended: '+t.sID); 3163 t._onfinish(); 3164 3165 }), 3166 3167 error: _html5_event(function(e) { 3168 3169 _s._wD(_h5+'error: '+this.error.code); 3170 // call load with error state? 3171 this._t._onload(false); 3172 3173 }), 3174 3175 loadeddata: _html5_event(function(e) { 3176 3177 var t = this._t, 3178 // at least 1 byte, so math works 3179 bytesTotal = t.bytesTotal || 1; 3180 3181 _s._wD(_h5+'loadeddata: '+this._t.sID); 3182 3183 // safari seems to nicely report progress events, eventually totalling 100% 3184 if (!t._loaded && !_isSafari) { 3185 t.duration = t._get_html5_duration(); 3186 // fire whileloading() with 100% values 3187 t._whileloading(bytesTotal, bytesTotal, t._get_html5_duration()); 3188 t._onload(true); 3189 } 3190 3191 }), 3192 3193 loadedmetadata: _html5_event(function(e) { 3194 3195 _s._wD(_h5+'loadedmetadata: '+this._t.sID); 3196 3197 }), 3198 3199 loadstart: _html5_event(function(e) { 3200 3201 _s._wD(_h5+'loadstart: '+this._t.sID); 3202 // assume buffering at first 3203 this._t._onbufferchange(1); 3204 3205 }), 3206 3207 play: _html5_event(function(e) { 3208 3209 _s._wD(_h5+'play: '+this._t.sID+', '+this._t.url); 3210 // once play starts, no buffering 3211 this._t._onbufferchange(0); 3212 3213 }), 3214 3215 playing: _html5_event(function(e) { 3216 3217 _s._wD(_h5+'playing: '+this._t.sID); 3218 3219 // once play starts, no buffering 3220 this._t._onbufferchange(0); 3221 3222 }), 3223 3224 progress: _html5_event(function(e) { 3225 3226 var t = this._t; 3227 3228 if (t.loaded) { 3229 return false;
3230 } 3231 3232 var i, j, str, buffered = 0, 3233 isProgress = (e.type === 'progress'), 3234 ranges = e.target.buffered, 3235 3236 // firefox 3.6 implements e.loaded/total (bytes) 3237 loaded = (e.loaded||0), 3238 3239 total = (e.total||1); 3240 3241 if (ranges && ranges.length) { 3242 3243 // if loaded is 0, try TimeRanges implementation as % of load 3244 // https://developer.mozilla.org/en/DOM/TimeRanges 3245 3246 for (i=ranges.length; i--;) { 3247 buffered = (ranges.end(i) - ranges.start(i)); 3248 } 3249 3250 // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges 3251 loaded = buffered/e.target.duration; 3252 3253 // <d> 3254 if (isProgress && ranges.length > 1) { 3255 str = []; 3256 j = ranges.length; 3257 for (i=0; i<j; i++) { 3258 str.push(e.target.buffered.start(i) +'-'+ e.target.buffered.end(i)); 3259 } 3260 _s._wD(_h5+'progress: timeRanges: '+str.join(', ')); 3261 } 3262 3263 if (isProgress && !isNaN(loaded)) { 3264 _s._wD(_h5+'progress: '+t.sID+': ' + Math.floor(loaded*100)+'% loaded'); 3265 } 3266 // </d> 3267 3268 } 3269 3270 if (!isNaN(loaded)) { 3271 3272 // if progress, likely not buffering 3273 t._onbufferchange(0); 3274 t._whileloading(loaded, total, t._get_html5_duration()); 3275 if (loaded && total && loaded === total) { 3276 // in case "onload" doesn't fire (eg. gecko 1.9.2) 3277 _html5_events.load.call(this, e); 3278 } 3279 3280 } 3281 3282 }), 3283 3284 ratechange: _html5_event(function(e) { 3285 3286 _s._wD(_h5+'ratechange: '+this._t.sID); 3287 3288 }), 3289 3290 suspend: _html5_event(function(e) { 3291 3292 // download paused/stopped, may have finished (eg. onload) 3293 var t = this._t; 3294 3295 _s._wD(_h5+'suspend: '+t.sID); 3296 _html5_events.progress.call(this, e); 3297 t._onsuspend(); 3298 3299 }), 3300 3301 stalled: _html5_event(function(e) { 3302 3303 _s._wD(_h5+'stalled: '+this._t.sID); 3304 3305 }), 3306 3307 timeupdate: _html5_event(function(e) { 3308 3309 this._t._onTimer(); 3310 3311 }), 3312 3313 waiting: _html5_event(function(e) { 3314 3315 var t = this._t; 3316 3317 // see also: seeking 3318 _s._wD(_h5+'waiting: '+t.sID); 3319 3320 // playback faster than download rate, etc. 3321 t._onbufferchange(1); 3322 3323 }) 3324 3325 }; 3326 3327 _html5OK = function(iO) { 3328 3329 // Use type, if specified. If HTML5-only mode, no other options, so just give 'er 3330 return (!iO.serverURL && (iO.type?_html5CanPlay({type:iO.type}):_html5CanPlay({url:iO.url})||_s.html5Only)); 3331 3332 }; 3333 3334 _html5Unload = function(oAudio) { 3335 3336 /** 3337 * Internal method: Unload media, and cancel any current/pending network requests. 3338 * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download. 3339 * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media 3340 * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL. 3341 */ 3342 3343 if (oAudio) { 3344 // Firefox likes '' for unload, most other UAs don't and fail to unload. 3345 oAudio.src = (_is_firefox ? '' : _emptyURL); 3346 } 3347 3348 }; 3349 3350 _html5CanPlay = function(o) { 3351 3352 /** 3353 * Try to find MIME, test and return truthiness 3354 * o = { 3355 * url: '/path/to/an.mp3', 3356 * type: 'audio/mp3' 3357 * } 3358 */ 3359 3360 if (!_s.useHTML5Audio || !_s.hasHTML5) { 3361 return false; 3362 } 3363 3364 var url = (o.url || null), 3365 mime = (o.type || null), 3366 aF = _s.audioFormats, 3367 result, 3368 offset, 3369 fileExt, 3370 item; 3371 3372 function preferFlashCheck(kind) { 3373 3374 // whether flash should play a given type 3375 return (_s.preferFlash && _hasFlash && !_s.ignoreFlash && (typeof _s.flash[kind] !== 'undefined' && _s.flash[kind])); 3376 3377 } 3378 3379 // account for known cases like audio/mp3 3380 3381 if (mime && _s.html5[mime] !== 'undefined') { 3382 return (_s.html5[mime] && !preferFlashCheck(mime)); 3383 } 3384 3385 if (!_html5Ext) { 3386 _html5Ext = []; 3387 for (item in aF) { 3388 if (aF.hasOwnProperty(item)) { 3389 _html5Ext.push(item); 3390 if (aF[item].related) { 3391 _html5Ext = _html5Ext.concat(aF[item].related); 3392 } 3393 } 3394 } 3395 _html5Ext = new RegExp('\\.('+_html5Ext.join('|')+')(\\?.*)?$','i'); 3396 } 3397 3398 // TODO: Strip URL queries, etc. 3399 fileExt = (url ? url.toLowerCase().match(_html5Ext) : null); 3400 3401 if (!fileExt || !fileExt.length) { 3402 if (!mime) { 3403 return false;
3404 } else { 3405 // audio/mp3 -> mp3, result should be known 3406 offset = mime.indexOf(';'); 3407 // strip "audio/X; codecs.." 3408 fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6); 3409 } 3410 } else { 3411 // match the raw extension name - "mp3", for example 3412 fileExt = fileExt[1]; 3413 } 3414 3415 if (fileExt && typeof _s.html5[fileExt] !== 'undefined') { 3416 // result known 3417 return (_s.html5[fileExt] && !preferFlashCheck(fileExt)); 3418 } else { 3419 mime = 'audio/'+fileExt; 3420 result = _s.html5.canPlayType({type:mime}); 3421 _s.html5[fileExt] = result; 3422 // _s._wD('canPlayType, found result: '+result); 3423 return (result && _s.html5[mime] && !preferFlashCheck(mime)); 3424 } 3425 3426 }; 3427 3428 _testHTML5 = function() { 3429 3430 if (!_s.useHTML5Audio || typeof Audio === 'undefined') { 3431 return false; 3432 } 3433 3434 // double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/ 3435 var a = (typeof Audio !== 'undefined' ? (_isOpera ? new Audio(null) : new Audio()) : null), 3436 item, support = {}, aF, i; 3437 3438 function _cp(m) { 3439 3440 var canPlay, i, j, isOK = false; 3441 3442 if (!a || typeof a.canPlayType !== 'function') { 3443 return false; 3444 } 3445 3446 if (m instanceof Array) { 3447 // iterate through all mime types, return any successes 3448 for (i=0, j=m.length; i<j && !isOK; i++) { 3449 if (_s.html5[m[i]] || a.canPlayType(m[i]).match(_s.html5Test)) { 3450 isOK = true; 3451 _s.html5[m[i]] = true; 3452 3453 // if flash can play and preferred, also mark it for use. 3454 _s.flash[m[i]] = !!(_s.preferFlash && _hasFlash && m[i].match(_flashMIME)); 3455 3456 } 3457 } 3458 return isOK; 3459 } else { 3460 canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false); 3461 return !!(canPlay && (canPlay.match(_s.html5Test))); 3462 } 3463 3464 } 3465 3466 // test all registered formats + codecs 3467 3468 aF = _s.audioFormats; 3469 3470 for (item in aF) { 3471 if (aF.hasOwnProperty(item)) { 3472 support[item] = _cp(aF[item].type); 3473 3474 // write back generic type too, eg. audio/mp3 3475 support['audio/'+item] = support[item]; 3476 3477 // assign flash 3478 if (_s.preferFlash && !_s.ignoreFlash && item.match(_flashMIME)) { 3479 _s.flash[item] = true; 3480 } else { 3481 _s.flash[item] = false; 3482 } 3483 3484 // assign result to related formats, too 3485 if (aF[item] && aF[item].related) { 3486 for (i=aF[item].related.length; i--;) { 3487 // eg. audio/m4a 3488 support['audio/'+aF[item].related[i]] = support[item]; 3489 _s.html5[aF[item].related[i]] = support[item]; 3490 _s.flash[aF[item].related[i]] = support[item]; 3491 } 3492 } 3493 } 3494 } 3495 3496 support.canPlayType = (a?_cp:null); 3497 _s.html5 = _mixin(_s.html5, support); 3498 3499 return true; 3500 3501 }; 3502 3503 _strings = { 3504 3505 // <d> 3506 notReady: 'Not loaded yet - wait for soundManager.onload()/onready()', 3507 notOK: 'Audio support is not available.', 3508 domError: _smc + 'createMovie(): appendChild/innerHTML call failed. DOM not ready or other error.', 3509 spcWmode: _smc + 'createMovie(): Removing wmode, preventing known SWF loading issue(s)', 3510 swf404: _sm + ': Verify that %s is a valid path.', 3511 tryDebug: 'Try ' + _sm + '.debugFlash = true for more security details (output goes to SWF.)', 3512 checkSWF: 'See SWF output for more debug info.', 3513 localFail: _sm + ': Non-HTTP page (' + _doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/', 3514 waitFocus: _sm + ': Special case: Waiting for focus-related event..', 3515 waitImpatient: _sm + ': Getting impatient, still waiting for Flash%s...', 3516 waitForever: _sm + ': Waiting indefinitely for Flash (will recover if unblocked)...', 3517 needFunction: _sm + ': Function object expected for %s', 3518 badID: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character', 3519 currentObj: '--- ' + _sm + '._debug(): Current sound objects ---',
3520 waitEI: _smc + 'initMovie(): Waiting for ExternalInterface call from Flash..', 3521 waitOnload: _sm + ': Waiting for window.onload()', 3522 docLoaded: _sm + ': Document already loaded', 3523 onload: _smc + 'initComplete(): calling soundManager.onload()', 3524 onloadOK: _sm + '.onload() complete', 3525 init: _smc + 'init()', 3526 didInit: _smc + 'init(): Already called?', 3527 flashJS: _sm + ': Attempting to call Flash from JS..', 3528 secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html', 3529 badRemove: 'Warning: Failed to remove flash movie.', 3530 noPeak: 'Warning: peakData features unsupported for movieStar formats', 3531 shutdown: _sm + '.disable(): Shutting down', 3532 queue: _sm + ': Queueing %s handler', 3533 smFail: _sm + ': Failed to initialise.', 3534 smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.', 3535 fbTimeout: 'No flash response, applying .'+_swfCSS.swfTimedout+' CSS..', 3536 fbLoaded: 'Flash loaded', 3537 fbHandler: _smc+'flashBlockHandler()', 3538 manURL: 'SMSound.load(): Using manually-assigned URL', 3539 onURL: _sm + '.load(): current URL already assigned.', 3540 badFV: _sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.', 3541 as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)', 3542 noNSLoop: 'Note: Looping not implemented for MovieStar formats', 3543 needfl9: 'Note: Switching to flash 9, required for MP4 formats.', 3544 mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case', 3545 mfOn: 'mobileFlash::enabling on-screen flash repositioning', 3546 policy: 'Enabling usePolicyFile for data access' 3547 // </d> 3548 3549 }; 3550 3551 _str = function() { 3552 3553 // internal string replace helper. 3554 // arguments: o [,items to replace] 3555 // <d> 3556 3557 // real array, please 3558 var args = _slice.call(arguments), 3559 3560 // first arg 3561 o = args.shift(), 3562 3563 str = (_strings && _strings[o]?_strings[o]:''), i, j; 3564 if (str && args && args.length) { 3565 for (i = 0, j = args.length; i < j; i++) { 3566 str = str.replace('%s', args[i]); 3567 } 3568 } 3569 3570 return str; 3571 // </d> 3572 3573 }; 3574 3575 _loopFix = function(sOpt) { 3576 3577 // flash 8 requires stream = false for looping to work 3578 if (_fV === 8 && sOpt.loops > 1 && sOpt.stream) { 3579 _wDS('as2loop'); 3580 sOpt.stream = false; 3581 } 3582 3583 return sOpt; 3584 3585 }; 3586 3587 _policyFix = function(sOpt, sPre) { 3588 3589 if (sOpt && !sOpt.usePolicyFile && (sOpt.onid3 || sOpt.usePeakData || sOpt.useWaveformData || sOpt.useEQData)) { 3590 _s._wD((sPre || '') + _str('policy')); 3591 sOpt.usePolicyFile = true; 3592 } 3593 3594 return sOpt; 3595 3596 }; 3597 3598 _complain = function(sMsg) { 3599 3600 // <d> 3601 if (typeof console !== 'undefined' && typeof console.warn !== 'undefined') { 3602 console.warn(sMsg); 3603 } else { 3604 _s._wD(sMsg); 3605 } 3606 // </d> 3607 3608 }; 3609 3610 _doNothing = function() { 3611 3612 return false; 3613 3614 }; 3615 3616 _disableObject = function(o) { 3617 3618 var oProp; 3619 3620 for (oProp in o) { 3621 if (o.hasOwnProperty(oProp) && typeof o[oProp] === 'function') { 3622 o[oProp] = _doNothing; 3623 } 3624 } 3625 3626 oProp = null; 3627 3628 }; 3629 3630 _failSafely = function(bNoDisable) { 3631 3632 // general failure exception handler 3633 3634 if (typeof bNoDisable === 'undefined') { 3635 bNoDisable = false; 3636 } 3637 3638 if (_disabled || bNoDisable) { 3639 _wDS('smFail', 2); 3640 _s.disable(bNoDisable); 3641 } 3642 3643 }; 3644 3645 _normalizeMovieURL = function(smURL) { 3646 3647 var urlParams = null, url; 3648 3649 if (smURL) { 3650 if (smURL.match(/\.swf(\?.*)?$/i)) { 3651 urlParams = smURL.substr(smURL.toLowerCase().lastIndexOf('.swf?') + 4); 3652 if (urlParams) { 3653 // assume user knows what they're doing 3654 return smURL; 3655 } 3656 } else if (smURL.lastIndexOf('/') !== smURL.length - 1) { 3657 // append trailing slash, if needed 3658 smURL += '/'; 3659 } 3660 } 3661 3662 url = (smURL && smURL.lastIndexOf('/') !== - 1 ? smURL.substr(0, smURL.lastIndexOf('/') + 1) : './') + _s.movieURL; 3663 3664 if (_s.noSWFCache) { 3665 url += ('?ts=' + new Date().getTime()); 3666 } 3667 3668 return url; 3669 3670 }; 3671 3672 _setVersionInfo = function() { 3673 3674 // short-hand for internal use 3675 3676 _fV = parseInt(_s.flashVersion, 10); 3677 3678 if (_fV !== 8 && _fV !== 9) { 3679 _s._wD(_str('badFV', _fV, _defaultFlashVersion)); 3680 _s.flashVersion = _fV = _defaultFlashVersion; 3681 } 3682 3683 // debug flash movie, if applicable 3684 3685 var isDebug = (_s.debugMode || _s.debugFlash?'_debug.swf':'.swf'); 3686 3687 if (_s.useHTML5Audio && !_s.html5Only && _s.audioFormats.mp4.required && _fV < 9) { 3688 _s._wD(_str('needfl9')); 3689 _s.flashVersion = _fV = 9; 3690 } 3691 3692 _s.version = _s.versionNumber + (_s.html5Only?' (HTML5-only mode)':(_fV === 9?' (AS3/Flash 9)':' (AS2/Flash 8)')); 3693 3694 // set up default options 3695 if (_fV > 8) { 3696 // +flash 9 base options 3697 _s.defaultOptions = _mixin(_s.defaultOptions, _s.flash9Options); 3698 _s.features.buffering = true; 3699 // +moviestar support 3700 _s.defaultOptions = _mixin(_s.defaultOptions, _s.movieStarOptions); 3701 _s.filePatterns.flash9 = new RegExp('\\.(mp3|' + _netStreamTypes.join('|') + ')(\\?.*)?$', 'i'); 3702 _s.features.movieStar = true; 3703 } else { 3704 _s.features.movieStar = false;
3705 } 3706 3707 // regExp for flash canPlay(), etc. 3708 _s.filePattern = _s.filePatterns[(_fV !== 8?'flash9':'flash8')]; 3709 3710 // if applicable, use _debug versions of SWFs 3711 _s.movieURL = (_fV === 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug); 3712 3713 _s.features.peakData = _s.features.waveformData = _s.features.eqData = (_fV > 8); 3714 3715 }; 3716 3717 _setPolling = function(bPolling, bHighPerformance) { 3718 3719 if (!_flash) { 3720 return false; 3721 } 3722 3723 _flash._setPolling(bPolling, bHighPerformance); 3724 3725 }; 3726 3727 _initDebug = function() { 3728 3729 // starts debug mode, creating output <div> for UAs without console object 3730 3731 // allow force of debug mode via URL 3732 if (_s.debugURLParam.test(_wl)) { 3733 _s.debugMode = true; 3734 } 3735 3736 // <d> 3737 if (_id(_s.debugID)) { 3738 return false; 3739 } 3740 3741 var oD, oDebug, oTarget, oToggle, tmp; 3742 3743 if (_s.debugMode && !_id(_s.debugID) && (!_hasConsole || !_s.useConsole || !_s.consoleOnly)) { 3744 3745 oD = _doc.createElement('div'); 3746 oD.id = _s.debugID + '-toggle'; 3747 3748 oToggle = { 3749 'position': 'fixed', 3750 'bottom': '0px', 3751 'right': '0px', 3752 'width': '1.2em', 3753 'height': '1.2em', 3754 'lineHeight': '1.2em', 3755 'margin': '2px', 3756 'textAlign': 'center', 3757 'border': '1px solid #999', 3758 'cursor': 'pointer', 3759 'background': '#fff', 3760 'color': '#333', 3761 'zIndex': 10001 3762 }; 3763 3764 oD.appendChild(_doc.createTextNode('-')); 3765 oD.onclick = _toggleDebug; 3766 oD.title = 'Toggle SM2 debug console'; 3767 3768 if (_ua.match(/msie 6/i)) { 3769 oD.style.position = 'absolute'; 3770 oD.style.cursor = 'hand'; 3771 } 3772 3773 for (tmp in oToggle) { 3774 if (oToggle.hasOwnProperty(tmp)) { 3775 oD.style[tmp] = oToggle[tmp]; 3776 } 3777 } 3778 3779 oDebug = _doc.createElement('div'); 3780 oDebug.id = _s.debugID; 3781 oDebug.style.display = (_s.debugMode?'block':'none'); 3782 3783 if (_s.debugMode && !_id(oD.id)) { 3784 try { 3785 oTarget = _getDocument(); 3786 oTarget.appendChild(oD); 3787 } catch(e2) { 3788 throw new Error(_str('domError')+' \n'+e2.toString()); 3789 } 3790 oTarget.appendChild(oDebug); 3791 } 3792 3793 } 3794 3795 oTarget = null; 3796 // </d> 3797 3798 }; 3799 3800 _idCheck = this.getSoundById; 3801 3802 // <d> 3803 _wDS = function(o, errorLevel) { 3804 3805 if (!o) { 3806 return ''; 3807 } else { 3808 return _s._wD(_str(o), errorLevel); 3809 } 3810 3811 }; 3812 3813 // last-resort debugging option 3814 3815 if (_wl.indexOf('sm2-debug=alert') + 1 && _s.debugMode) { 3816 _s._wD = function(sText) {window.alert(sText);}; 3817 } 3818 3819 _toggleDebug = function() { 3820 3821 var o = _id(_s.debugID), 3822 oT = _id(_s.debugID + '-toggle'); 3823 3824 if (!o) { 3825 return false; 3826 } 3827 3828 if (_debugOpen) { 3829 // minimize 3830 oT.innerHTML = '+'; 3831 o.style.display = 'none'; 3832 } else { 3833 oT.innerHTML = '-'; 3834 o.style.display = 'block'; 3835 } 3836 3837 _debugOpen = !_debugOpen; 3838 3839 }; 3840 3841 _debugTS = function(sEventType, bSuccess, sMessage) { 3842 3843 // troubleshooter debug hooks 3844 3845 if (typeof sm2Debugger !== 'undefined') { 3846 try { 3847 sm2Debugger.handleEvent(sEventType, bSuccess, sMessage); 3848 } catch(e) { 3849 // oh well 3850 } 3851 } 3852 3853 return true; 3854 3855 }; 3856 // </d> 3857 3858 _getSWFCSS = function() { 3859 3860 var css = []; 3861 3862 if (_s.debugMode) { 3863 css.push(_swfCSS.sm2Debug); 3864 } 3865 3866 if (_s.debugFlash) { 3867 css.push(_swfCSS.flashDebug); 3868 } 3869 3870 if (_s.useHighPerformance) { 3871 css.push(_swfCSS.highPerf); 3872 } 3873 3874 return css.join(' '); 3875 3876 }; 3877 3878 _flashBlockHandler = function() { 3879 3880 // *possible* flash block situation. 3881 3882 var name = _str('fbHandler'), 3883 p = _s.getMoviePercent(), 3884 css = _swfCSS, 3885 error = {type:'FLASHBLOCK'}; 3886 3887 if (_s.html5Only) { 3888 return false; 3889 } 3890 3891 if (!_s.ok()) { 3892 3893 if (_needsFlash) { 3894 // make the movie more visible, so user can fix 3895 _s.oMC.className = _getSWFCSS() + ' ' + css.swfDefault + ' ' + (p === null?css.swfTimedout:css.swfError); 3896 _s._wD(name+': '+_str('fbTimeout')+(p?' ('+_str('fbLoaded')+')':'')); 3897 } 3898 3899 _s.didFlashBlock = true; 3900 3901 // fire onready(), complain lightly 3902 _processOnEvents({type:'ontimeout', ignoreInit:true, error:error}); 3903 _catchError(error); 3904 3905 } else { 3906 3907 // SM2 loaded OK (or recovered) 3908 3909 // <d> 3910 if (_s.didFlashBlock) { 3911 _s._wD(name+': Unblocked'); 3912 } 3913 // </d> 3914 3915 if (_s.oMC) { 3916 _s.oMC.className = [_getSWFCSS(), css.swfDefault, css.swfLoaded + (_s.didFlashBlock?' '+css.swfUnblocked:'')].join(' '); 3917 } 3918 3919 } 3920 3921 }; 3922 3923 _addOnEvent = function(sType, oMethod, oScope) { 3924 3925 if (typeof _on_queue[sType] === 'undefined') { 3926 _on_queue[sType] = []; 3927 } 3928 3929 _on_queue[sType].push({ 3930 'method': oMethod, 3931 'scope': (oScope || null), 3932 'fired': false 3933 }); 3934 3935 }; 3936 3937 _processOnEvents = function(oOptions) { 3938 3939 // assume onready, if unspecified 3940 3941 if (!oOptions) { 3942 oOptions = { 3943 type: 'onready' 3944 }; 3945 } 3946 3947 if (!_didInit && oOptions && !oOptions.ignoreInit) { 3948 // not ready yet. 3949 return false;
3950 } 3951 3952 if (oOptions.type === 'ontimeout' && _s.ok()) { 3953 // invalid case 3954 return false; 3955 } 3956 3957 var status = { 3958 success: (oOptions && oOptions.ignoreInit?_s.ok():!_disabled) 3959 }, 3960 3961 // queue specified by type, or none 3962 srcQueue = (oOptions && oOptions.type?_on_queue[oOptions.type]||[]:[]), 3963 3964 queue = [], i, j, 3965 args = [status], 3966 canRetry = (_needsFlash && _s.useFlashBlock && !_s.ok()); 3967 3968 if (oOptions.error) { 3969 args[0].error = oOptions.error; 3970 } 3971 3972 for (i = 0, j = srcQueue.length; i < j; i++) { 3973 if (srcQueue[i].fired !== true) { 3974 queue.push(srcQueue[i]); 3975 } 3976 } 3977 3978 if (queue.length) { 3979 _s._wD(_sm + ': Firing ' + queue.length + ' '+oOptions.type+'() item' + (queue.length === 1?'':'s')); 3980 for (i = 0, j = queue.length; i < j; i++) { 3981 if (queue[i].scope) { 3982 queue[i].method.apply(queue[i].scope, args); 3983 } else { 3984 queue[i].method.apply(this, args); 3985 } 3986 if (!canRetry) { 3987 // flashblock case doesn't count here 3988 queue[i].fired = true; 3989 } 3990 } 3991 } 3992 3993 return true; 3994 3995 }; 3996 3997 _initUserOnload = function() { 3998 3999 _win.setTimeout(function() { 4000 4001 if (_s.useFlashBlock) { 4002 _flashBlockHandler(); 4003 } 4004 4005 _processOnEvents(); 4006 4007 // call user-defined "onload", scoped to window 4008 4009 if (_s.onload instanceof Function) { 4010 _wDS('onload', 1); 4011 _s.onload.apply(_win); 4012 _wDS('onloadOK', 1); 4013 } 4014 4015 if (_s.waitForWindowLoad) { 4016 _event.add(_win, 'load', _initUserOnload); 4017 } 4018 4019 },1); 4020 4021 }; 4022 4023 _detectFlash = function() { 4024 4025 // hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt 4026 4027 if (_hasFlash !== undefined) { 4028 // this work has already been done. 4029 return _hasFlash; 4030 } 4031 4032 var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = _win.ActiveXObject; 4033 4034 if (nP && nP.length) { 4035 type = 'application/x-shockwave-flash'; 4036 types = n.mimeTypes; 4037 if (types && types[type] && types[type].enabledPlugin && types[type].enabledPlugin.description) { 4038 hasPlugin = true; 4039 } 4040 } else if (typeof AX !== 'undefined') { 4041 try { 4042 obj = new AX('ShockwaveFlash.ShockwaveFlash'); 4043 } catch(e) { 4044 // oh well 4045 } 4046 hasPlugin = (!!obj); 4047 } 4048 4049 _hasFlash = hasPlugin; 4050 4051 return hasPlugin; 4052 4053 }; 4054 4055 _featureCheck = function() { 4056 4057 var needsFlash, item, 4058 4059 // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (iPad) + iOS4 works. 4060 isSpecial = (_is_iDevice && !!(_ua.match(/os (1|2|3_0|3_1)/i))); 4061 4062 if (isSpecial) { 4063 4064 // has Audio(), but is broken; let it load links directly. 4065 _s.hasHTML5 = false; 4066 4067 // ignore flash case, however 4068 _s.html5Only = true; 4069 4070 if (_s.oMC) { 4071 _s.oMC.style.display = 'none'; 4072 } 4073 4074 return false; 4075 4076 } 4077 4078 if (_s.useHTML5Audio) { 4079 4080 if (!_s.html5 || !_s.html5.canPlayType) { 4081 _s._wD('SoundManager: No HTML5 Audio() support detected.'); 4082 _s.hasHTML5 = false; 4083 return true; 4084 } else { 4085 _s.hasHTML5 = true; 4086 } 4087 if (_isBadSafari) { 4088 _s._wD(_smc+'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - '+(!_hasFlash?' would use flash fallback for MP3/MP4, but none detected.':'will use flash fallback for MP3/MP4, if available'),1); 4089 if (_detectFlash()) { 4090 return true; 4091 } 4092 } 4093 } else { 4094 4095 // flash needed (or, HTML5 needs enabling.) 4096 return true; 4097 4098 } 4099 4100 for (item in _s.audioFormats) { 4101 if (_s.audioFormats.hasOwnProperty(item)) { 4102 if ((_s.audioFormats[item].required && !_s.html5.canPlayType(_s.audioFormats[item].type)) || _s.flash[item] || _s.flash[_s.audioFormats[item].type]) { 4103 // flash may be required, or preferred for this format 4104 needsFlash = true; 4105 } 4106 } 4107 } 4108 4109 // sanity check.. 4110 if (_s.ignoreFlash) { 4111 needsFlash = false;
4112 } 4113 4114 _s.html5Only = (_s.hasHTML5 && _s.useHTML5Audio && !needsFlash); 4115 4116 return (!_s.html5Only); 4117 4118 }; 4119 4120 _parseURL = function(url) { 4121 4122 /** 4123 * Internal: Finds and returns the first playable URL (or failing that, the first URL.) 4124 * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects. 4125 */ 4126 4127 var i, j, result = 0; 4128 4129 if (url instanceof Array) { 4130 4131 // find the first good one 4132 for (i=0, j=url.length; i<j; i++) { 4133 4134 if (url[i] instanceof Object) { 4135 // MIME check 4136 if (_s.canPlayMIME(url[i].type)) { 4137 result = i; 4138 break; 4139 } 4140 4141 } else if (_s.canPlayURL(url[i])) { 4142 // URL string check 4143 result = i; 4144 break; 4145 } 4146 4147 } 4148 4149 // normalize to string 4150 if (url[result].url) { 4151 url[result] = url[result].url; 4152 } 4153 4154 return url[result]; 4155 4156 } else { 4157 4158 // single URL case 4159 return url; 4160 4161 } 4162 4163 }; 4164 4165 4166 _startTimer = function(oSound) { 4167 4168 /** 4169 * attach a timer to this sound, and start an interval if needed 4170 */ 4171 4172 if (!oSound._hasTimer) { 4173 4174 oSound._hasTimer = true; 4175 4176 if (!_likesHTML5 && _s.html5PollingInterval) { 4177 4178 if (_h5IntervalTimer === null && _h5TimerCount === 0) { 4179 4180 _h5IntervalTimer = window.setInterval(_timerExecute, _s.html5PollingInterval); 4181 4182 } 4183 4184 _h5TimerCount++; 4185 4186 } 4187 4188 } 4189 4190 }; 4191 4192 _stopTimer = function(oSound) { 4193 4194 /** 4195 * detach a timer 4196 */ 4197 4198 if (oSound._hasTimer) { 4199 4200 oSound._hasTimer = false; 4201 4202 if (!_likesHTML5 && _s.html5PollingInterval) { 4203 4204 // interval will stop itself at next execution. 4205 4206 _h5TimerCount--; 4207 4208 } 4209 4210 } 4211 4212 }; 4213 4214 _timerExecute = function() { 4215 4216 /** 4217 * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval) 4218 */ 4219 4220 var i, j; 4221 4222 if (_h5IntervalTimer !== null && !_h5TimerCount) { 4223 4224 // no active timers, stop polling interval. 4225 4226 window.clearInterval(_h5IntervalTimer); 4227 4228 _h5IntervalTimer = null; 4229 4230 return false; 4231 4232 } 4233 4234 // check all HTML5 sounds with timers 4235 4236 for (i = _s.soundIDs.length; i--;) { 4237 4238 if (_s.sounds[_s.soundIDs[i]].isHTML5 && _s.sounds[_s.soundIDs[i]]._hasTimer) { 4239 4240 _s.sounds[_s.soundIDs[i]]._onTimer(); 4241 4242 } 4243 4244 } 4245 4246 }; 4247 4248 _catchError = function(options) { 4249 4250 options = (typeof options !== 'undefined' ? options : {}); 4251 4252 if (_s.onerror instanceof Function) { 4253 _s.onerror.apply(_win, [{type:(typeof options.type !== 'undefined' ? options.type : null)}]); 4254 } 4255 4256 if (typeof options.fatal !== 'undefined' && options.fatal) { 4257 _s.disable(); 4258 } 4259 4260 }; 4261 4262 _badSafariFix = function() { 4263 4264 // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4 4265 if (!_isBadSafari || !_detectFlash()) { 4266 // doesn't apply 4267 return false; 4268 } 4269 4270 var aF = _s.audioFormats, i, item; 4271 4272 for (item in aF) { 4273 if (aF.hasOwnProperty(item)) { 4274 if (item === 'mp3' || item === 'mp4') { 4275 _s._wD(_sm+': Using flash fallback for '+item+' format'); 4276 _s.html5[item] = false; 4277 // assign result to related formats, too 4278 if (aF[item] && aF[item].related) { 4279 for (i = aF[item].related.length; i--;) { 4280 _s.html5[aF[item].related[i]] = false; 4281 } 4282 } 4283 } 4284 } 4285 } 4286 4287 }; 4288 4289 /** 4290 * Pseudo-private flash/ExternalInterface methods 4291 * ---------------------------------------------- 4292 */ 4293 4294 this._setSandboxType = function(sandboxType) { 4295 4296 // <d> 4297 var sb = _s.sandbox; 4298 4299 sb.type = sandboxType; 4300 sb.description = sb.types[(typeof sb.types[sandboxType] !== 'undefined'?sandboxType:'unknown')]; 4301 4302 _s._wD('Flash security sandbox type: ' + sb.type); 4303 4304 if (sb.type === 'localWithFile') { 4305 4306 sb.noRemote = true; 4307 sb.noLocal = false;
4308 _wDS('secNote', 2); 4309 4310 } else if (sb.type === 'localWithNetwork') { 4311 4312 sb.noRemote = false; 4313 sb.noLocal = true; 4314 4315 } else if (sb.type === 'localTrusted') { 4316 4317 sb.noRemote = false; 4318 sb.noLocal = false; 4319 4320 } 4321 // </d> 4322 4323 }; 4324 4325 this._externalInterfaceOK = function(flashDate, swfVersion) { 4326 4327 // flash callback confirming flash loaded, EI working etc. 4328 // flashDate = approx. timing/delay info for JS/flash bridge 4329 // swfVersion: SWF build string 4330 4331 if (_s.swfLoaded) { 4332 return false; 4333 } 4334 4335 var e, eiTime = new Date().getTime(); 4336 4337 _s._wD(_smc+'externalInterfaceOK()' + (flashDate?' (~' + (eiTime - flashDate) + ' ms)':'')); 4338 _debugTS('swf', true); 4339 _debugTS('flashtojs', true); 4340 _s.swfLoaded = true; 4341 _tryInitOnFocus = false; 4342 4343 if (_isBadSafari) { 4344 _badSafariFix(); 4345 } 4346 4347 // complain if JS + SWF build/version strings don't match, excluding +DEV builds 4348 // <d> 4349 if (!swfVersion || swfVersion.replace(/\+dev/i,'') !== _s.versionNumber.replace(/\+dev/i, '')) { 4350 4351 e = _sm + ': Fatal: JavaScript file build "' + _s.versionNumber + '" does not match Flash SWF build "' + swfVersion + '" at ' + _s.url + '. Ensure both are up-to-date.'; 4352 4353 // escape flash -> JS stack so this error fires in window. 4354 setTimeout(function versionMismatch() { 4355 throw new Error(e); 4356 }, 0); 4357 4358 // exit, init will fail with timeout 4359 return false; 4360 4361 } 4362 // </d> 4363 4364 if (_isIE) { 4365 // IE needs a timeout OR delay until window.onload - may need TODO: investigating 4366 setTimeout(_init, 100); 4367 } else { 4368 _init(); 4369 } 4370 4371 }; 4372 4373 /** 4374 * Private initialization helpers 4375 * ------------------------------ 4376 */ 4377 4378 _createMovie = function(smID, smURL) { 4379 4380 if (_didAppend && _appendSuccess) { 4381 // ignore if already succeeded 4382 return false; 4383 } 4384 4385 function _initMsg() { 4386 _s._wD('-- SoundManager 2 ' + _s.version + (!_s.html5Only && _s.useHTML5Audio?(_s.hasHTML5?' + HTML5 audio':', no HTML5 audio support'):'') + (!_s.html5Only ? (_s.useHighPerformance?', high performance mode, ':', ') + (( _s.flashPollingInterval ? 'custom (' + _s.flashPollingInterval + 'ms)' : 'normal') + ' polling') + (_s.wmode?', wmode: ' + _s.wmode:'') + (_s.debugFlash?', flash debug mode':'') + (_s.useFlashBlock?', flashBlock mode':'') : '') + ' --', 1); 4387 } 4388 4389 if (_s.html5Only) { 4390 4391 // 100% HTML5 mode 4392 _setVersionInfo(); 4393 4394 _initMsg(); 4395 _s.oMC = _id(_s.movieID); 4396 _init(); 4397 4398 // prevent multiple init attempts 4399 _didAppend = true; 4400 4401 _appendSuccess = true; 4402 4403 return false; 4404 4405 } 4406 4407 // flash path 4408 var remoteURL = (smURL || _s.url), 4409 localURL = (_s.altURL || remoteURL), 4410 swfTitle = 'JS/Flash audio component (SoundManager 2)', 4411 oEmbed, oMovie, oTarget = _getDocument(), tmp, movieHTML, oEl, extraClass = _getSWFCSS(), 4412 s, x, sClass, side = 'auto', isRTL = null, 4413 html = _doc.getElementsByTagName('html')[0]; 4414 4415 isRTL = (html && html.dir && html.dir.match(/rtl/i)); 4416 smID = (typeof smID === 'undefined'?_s.id:smID); 4417 4418 function param(name, value) { 4419 return '<param name="'+name+'" value="'+value+'" />'; 4420 } 4421 4422 // safety check for legacy (change to Flash 9 URL) 4423 _setVersionInfo(); 4424 _s.url = _normalizeMovieURL(_overHTTP?remoteURL:localURL); 4425 smURL = _s.url; 4426 4427 _s.wmode = (!_s.wmode && _s.useHighPerformance ? 'transparent' : _s.wmode); 4428 4429 if (_s.wmode !== null && (_ua.match(/msie 8/i) || (!_isIE && !_s.useHighPerformance)) && navigator.platform.match(/win32|win64/i)) { 4430 /** 4431 * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here 4432 * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout 4433 * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?) 4434 */ 4435 _wDS('spcWmode'); 4436 _s.wmode = null; 4437 } 4438 4439 oEmbed = { 4440 'name': smID, 4441 'id': smID, 4442 'src': smURL, 4443 'width': side, 4444 'height': side, 4445 'quality': 'high', 4446 'allowScriptAccess': _s.allowScriptAccess, 4447 'bgcolor': _s.bgColor, 4448 'pluginspage': _http+'www.macromedia.com/go/getflashplayer', 4449 'title': swfTitle, 4450 'type': 'application/x-shockwave-flash', 4451 'wmode': _s.wmode, 4452 // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html 4453 'hasPriority': 'true' 4454 }; 4455 4456 if (_s.debugFlash) {
4457 oEmbed.FlashVars = 'debug=1'; 4458 } 4459 4460 if (!_s.wmode) { 4461 // don't write empty attribute 4462 delete oEmbed.wmode; 4463 } 4464 4465 if (_isIE) { 4466 4467 // IE is "special". 4468 oMovie = _doc.createElement('div'); 4469 movieHTML = [ 4470 '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + _http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" width="' + oEmbed.width + '" height="' + oEmbed.height + '">', 4471 param('movie', smURL), 4472 param('AllowScriptAccess', _s.allowScriptAccess), 4473 param('quality', oEmbed.quality), 4474 (_s.wmode? param('wmode', _s.wmode): ''), 4475 param('bgcolor', _s.bgColor), 4476 param('hasPriority', 'true'), 4477 (_s.debugFlash ? param('FlashVars', oEmbed.FlashVars) : ''), 4478 '</object>' 4479 ].join(''); 4480 4481 } else { 4482 4483 oMovie = _doc.createElement('embed'); 4484 for (tmp in oEmbed) { 4485 if (oEmbed.hasOwnProperty(tmp)) { 4486 oMovie.setAttribute(tmp, oEmbed[tmp]); 4487 } 4488 } 4489 4490 } 4491 4492 _initDebug(); 4493 extraClass = _getSWFCSS(); 4494 oTarget = _getDocument(); 4495 4496 if (oTarget) { 4497 4498 _s.oMC = (_id(_s.movieID) || _doc.createElement('div')); 4499 4500 if (!_s.oMC.id) { 4501 4502 _s.oMC.id = _s.movieID; 4503 _s.oMC.className = _swfCSS.swfDefault + ' ' + extraClass; 4504 s = null; 4505 oEl = null; 4506 4507 if (!_s.useFlashBlock) { 4508 if (_s.useHighPerformance) { 4509 // on-screen at all times 4510 s = { 4511 'position': 'fixed', 4512 'width': '8px', 4513 'height': '8px', 4514 // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes. 4515 'bottom': '0px', 4516 'left': '0px', 4517 'overflow': 'hidden' 4518 }; 4519 } else { 4520 // hide off-screen, lower priority 4521 s = { 4522 'position': 'absolute', 4523 'width': '6px', 4524 'height': '6px', 4525 'top': '-9999px', 4526 'left': '-9999px' 4527 }; 4528 if (isRTL) { 4529 s.left = Math.abs(parseInt(s.left,10))+'px'; 4530 } 4531 } 4532 } 4533 4534 if (_isWebkit) { 4535 // soundcloud-reported render/crash fix, safari 5 4536 _s.oMC.style.zIndex = 10000; 4537 } 4538 4539 if (!_s.debugFlash) { 4540 for (x in s) { 4541 if (s.hasOwnProperty(x)) { 4542 _s.oMC.style[x] = s[x]; 4543 } 4544 } 4545 } 4546 4547 try { 4548 if (!_isIE) { 4549 _s.oMC.appendChild(oMovie); 4550 } 4551 oTarget.appendChild(_s.oMC); 4552 if (_isIE) { 4553 oEl = _s.oMC.appendChild(_doc.createElement('div')); 4554 oEl.className = _swfCSS.swfBox; 4555 oEl.innerHTML = movieHTML; 4556 } 4557 _appendSuccess = true; 4558 } catch(e) { 4559 throw new Error(_str('domError')+' \n'+e.toString()); 4560 } 4561 4562 } else { 4563 4564 // SM2 container is already in the document (eg. flashblock use case) 4565 sClass = _s.oMC.className; 4566 _s.oMC.className = (sClass?sClass+' ':_swfCSS.swfDefault) + (extraClass?' '+extraClass:''); 4567 _s.oMC.appendChild(oMovie); 4568 if (_isIE) { 4569 oEl = _s.oMC.appendChild(_doc.createElement('div')); 4570 oEl.className = _swfCSS.swfBox; 4571 oEl.innerHTML = movieHTML; 4572 } 4573 _appendSuccess = true; 4574 4575 } 4576 4577 } 4578 4579 _didAppend = true; 4580 _initMsg(); 4581 _s._wD(_smc+'createMovie(): Trying to load ' + smURL + (!_overHTTP && _s.altURL?' (alternate URL)':''), 1); 4582 4583 return true; 4584 4585 }; 4586 4587 _initMovie = function() { 4588 4589 if (_s.html5Only) { 4590 _createMovie(); 4591 return false; 4592 } 4593 4594 // attempt to get, or create, movie 4595 // may already exist 4596 if (_flash) { 4597 return false; 4598 } 4599 4600 // inline markup case 4601 _flash = _s.getMovie(_s.id); 4602 4603 if (!_flash) { 4604 if (!_oRemoved) { 4605 // try to create 4606 _createMovie(_s.id, _s.url); 4607 } else { 4608 // try to re-append removed movie after reboot() 4609 if (!_isIE) { 4610 _s.oMC.appendChild(_oRemoved); 4611 } else { 4612 _s.oMC.innerHTML = _oRemovedHTML; 4613 } 4614 _oRemoved = null; 4615 _didAppend = true; 4616 } 4617 _flash = _s.getMovie(_s.id); 4618 } 4619 4620 // <d> 4621 if (_flash) { 4622 _wDS('waitEI'); 4623 } 4624 // </d> 4625 4626 if (_s.oninitmovie instanceof Function) { 4627 setTimeout(_s.oninitmovie, 1); 4628 } 4629 4630 return true; 4631 4632 }; 4633 4634 _delayWaitForEI = function() { 4635 4636 setTimeout(_waitForEI, 1000); 4637 4638 }; 4639 4640 _waitForEI = function() { 4641 4642 if (_waitingForEI) { 4643 return false;
4644 } 4645 4646 _waitingForEI = true; 4647 _event.remove(_win, 'load', _delayWaitForEI); 4648 4649 if (_tryInitOnFocus && !_isFocused) { 4650 // giant Safari 3.1 hack - assume mousemove = focus given lack of focus event 4651 _wDS('waitFocus'); 4652 return false; 4653 } 4654 4655 var p; 4656 if (!_didInit) { 4657 p = _s.getMoviePercent(); 4658 _s._wD(_str('waitImpatient', (p === 100?' (SWF loaded)':(p > 0?' (SWF ' + p + '% loaded)':'')))); 4659 } 4660 4661 setTimeout(function() { 4662 4663 p = _s.getMoviePercent(); 4664 4665 // <d> 4666 if (!_didInit) { 4667 _s._wD(_sm + ': No Flash response within expected time.\nLikely causes: ' + (p === 0?'Loading ' + _s.movieURL + ' may have failed (and/or Flash ' + _fV + '+ not present?), ':'') + 'Flash blocked or JS-Flash security error.' + (_s.debugFlash?' ' + _str('checkSWF'):''), 2); 4668 if (!_overHTTP && p) { 4669 _wDS('localFail', 2); 4670 if (!_s.debugFlash) { 4671 _wDS('tryDebug', 2); 4672 } 4673 } 4674 if (p === 0) { 4675 // if 0 (not null), probably a 404. 4676 _s._wD(_str('swf404', _s.url)); 4677 } 4678 _debugTS('flashtojs', false, ': Timed out' + _overHTTP?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)'); 4679 } 4680 // </d> 4681 4682 // give up / time-out, depending 4683 4684 if (!_didInit && _okToDisable) { 4685 if (p === null) { 4686 // SWF failed. Maybe blocked. 4687 if (_s.useFlashBlock || _s.flashLoadTimeout === 0) { 4688 if (_s.useFlashBlock) { 4689 _flashBlockHandler(); 4690 } 4691 _wDS('waitForever'); 4692 } else { 4693 // old SM2 behaviour, simply fail 4694 _failSafely(true); 4695 } 4696 } else { 4697 // flash loaded? Shouldn't be a blocking issue, then. 4698 if (_s.flashLoadTimeout === 0) { 4699 _wDS('waitForever'); 4700 } else { 4701 _failSafely(true); 4702 } 4703 } 4704 } 4705 4706 }, _s.flashLoadTimeout); 4707 4708 }; 4709 4710 _handleFocus = function() { 4711 4712 function cleanup() { 4713 _event.remove(_win, 'focus', _handleFocus); 4714 _event.remove(_win, 'load', _handleFocus); 4715 } 4716 4717 if (_isFocused || !_tryInitOnFocus) { 4718 cleanup(); 4719 return true; 4720 } 4721 4722 _okToDisable = true; 4723 _isFocused = true; 4724 _s._wD(_smc+'handleFocus()'); 4725 4726 if (_isSafari && _tryInitOnFocus) { 4727 _event.remove(_win, 'mousemove', _handleFocus); 4728 } 4729 4730 // allow init to restart 4731 _waitingForEI = false; 4732 4733 cleanup(); 4734 return true; 4735 4736 }; 4737 4738 _showSupport = function() { 4739 4740 var item, tests = []; 4741 4742 if (_s.useHTML5Audio && _s.hasHTML5) { 4743 for (item in _s.audioFormats) { 4744 if (_s.audioFormats.hasOwnProperty(item)) { 4745 tests.push(item + ': ' + _s.html5[item] + (!_s.html5[item] && _hasFlash && _s.flash[item] ? ' (using flash)' : (_s.preferFlash && _s.flash[item] && _hasFlash ? ' (preferring flash)': (!_s.html5[item] ? ' (' + (_s.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : '')))); 4746 } 4747 } 4748 _s._wD('-- SoundManager 2: HTML5 support tests ('+_s.html5Test+'): '+tests.join(', ')+' --',1); 4749 } 4750 4751 }; 4752 4753 _initComplete = function(bNoDisable) { 4754 4755 if (_didInit) { 4756 return false; 4757 } 4758 4759 if (_s.html5Only) { 4760 // all good. 4761 _s._wD('-- SoundManager 2: loaded --'); 4762 _didInit = true; 4763 _initUserOnload(); 4764 _debugTS('onload', true); 4765 return true; 4766 } 4767 4768 var wasTimeout = (_s.useFlashBlock && _s.flashLoadTimeout && !_s.getMoviePercent()), 4769 error; 4770 4771 if (!wasTimeout) {
4772 _didInit = true; 4773 if (_disabled) { 4774 error = {type: (!_hasFlash && _needsFlash ? 'NO_FLASH' : 'INIT_TIMEOUT')}; 4775 } 4776 } 4777 4778 _s._wD('-- SoundManager 2 ' + (_disabled?'failed to load':'loaded') + ' (' + (_disabled?'security/load error':'OK') + ') --', 1); 4779 4780 if (_disabled || bNoDisable) { 4781 if (_s.useFlashBlock && _s.oMC) { 4782 _s.oMC.className = _getSWFCSS() + ' ' + (_s.getMoviePercent() === null?_swfCSS.swfTimedout:_swfCSS.swfError); 4783 } 4784 _processOnEvents({type:'ontimeout', error:error}); 4785 _debugTS('onload', false); 4786 _catchError(error); 4787 return false; 4788 } else { 4789 _debugTS('onload', true); 4790 } 4791 4792 if (_s.waitForWindowLoad && !_windowLoaded) { 4793 _wDS('waitOnload'); 4794 _event.add(_win, 'load', _initUserOnload); 4795 return false; 4796 } else { 4797 // <d> 4798 if (_s.waitForWindowLoad && _windowLoaded) { 4799 _wDS('docLoaded'); 4800 } 4801 // </d> 4802 _initUserOnload(); 4803 } 4804 4805 return true; 4806 4807 }; 4808 4809 _init = function() { 4810 4811 _wDS('init'); 4812 4813 // called after onload() 4814 4815 if (_didInit) { 4816 _wDS('didInit'); 4817 return false; 4818 } 4819 4820 function _cleanup() { 4821 _event.remove(_win, 'load', _s.beginDelayedInit); 4822 } 4823 4824 if (_s.html5Only) { 4825 if (!_didInit) { 4826 // we don't need no steenking flash! 4827 _cleanup(); 4828 _s.enabled = true; 4829 _initComplete(); 4830 } 4831 return true; 4832 } 4833 4834 // flash path 4835 _initMovie(); 4836 4837 try { 4838 4839 _wDS('flashJS'); 4840 4841 // attempt to talk to Flash 4842 _flash._externalInterfaceTest(false); 4843 4844 // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling 4845 // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates) 4846 _setPolling(true, (_s.flashPollingInterval || (_s.useHighPerformance ? 10 : 50))); 4847 4848 if (!_s.debugMode) { 4849 // stop the SWF from making debug output calls to JS 4850 _flash._disableDebug(); 4851 } 4852 4853 _s.enabled = true; 4854 _debugTS('jstoflash', true); 4855 4856 if (!_s.html5Only) { 4857 // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead 4858 // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/ 4859 _event.add(_win, 'unload', _doNothing); 4860 } 4861 4862 } catch(e) { 4863 4864 _s._wD('js/flash exception: ' + e.toString()); 4865 _debugTS('jstoflash', false); 4866 _catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true}); 4867 // don't disable, for reboot() 4868 _failSafely(true); 4869 _initComplete(); 4870 4871 return false; 4872 4873 } 4874 4875 _initComplete(); 4876 4877 // disconnect events 4878 _cleanup(); 4879 4880 return true; 4881 4882 }; 4883 4884 _domContentLoaded = function() { 4885 4886 if (_didDCLoaded) { 4887 return false; 4888 } 4889 4890 _didDCLoaded = true; 4891 _initDebug(); 4892 4893 /** 4894 * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1 4895 * Ditto for sm2-preferFlash, too. 4896 */ 4897 // <d> 4898 (function(){ 4899 4900 var a = 'sm2-usehtml5audio=', l = _wl.toLowerCase(), b = null, 4901 a2 = 'sm2-preferflash=', b2 = null, hasCon = (typeof console !== 'undefined' && typeof console.log !== 'undefined'); 4902 4903 if (l.indexOf(a) !== -1) { 4904 b = (l.charAt(l.indexOf(a)+a.length) === '1'); 4905 if (hasCon) { 4906 console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter'); 4907 } 4908 _s.useHTML5Audio = b; 4909 } 4910 4911 if (l.indexOf(a2) !== -1) { 4912 b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1'); 4913 if (hasCon) { 4914 console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter'); 4915 } 4916 _s.preferFlash = b2; 4917 } 4918 4919 }()); 4920 // </d> 4921 4922 if (!_hasFlash && _s.hasHTML5) { 4923 _s._wD('SoundManager: No Flash detected'+(!_s.useHTML5Audio?', enabling HTML5.':'. Trying HTML5-only mode.')); 4924 _s.useHTML5Audio = true; 4925 // make sure we aren't preferring flash, either 4926 // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak. 4927 _s.preferFlash = false;
4928 } 4929 4930 _testHTML5(); 4931 _s.html5.usingFlash = _featureCheck(); 4932 _needsFlash = _s.html5.usingFlash; 4933 _showSupport(); 4934 4935 if (!_hasFlash && _needsFlash) { 4936 _s._wD('SoundManager: Fatal error: Flash is needed to play some required formats, but is not available.'); 4937 // TODO: Fatal here vs. timeout approach, etc. 4938 // hack: fail sooner. 4939 _s.flashLoadTimeout = 1; 4940 } 4941 4942 if (_doc.removeEventListener) { 4943 _doc.removeEventListener('DOMContentLoaded', _domContentLoaded, false); 4944 } 4945 4946 _initMovie(); 4947 return true; 4948 4949 }; 4950 4951 _domContentLoadedIE = function() { 4952 4953 if (_doc.readyState === 'complete') { 4954 _domContentLoaded(); 4955 _doc.detachEvent('onreadystatechange', _domContentLoadedIE); 4956 } 4957 4958 return true; 4959 4960 }; 4961 4962 _winOnLoad = function() { 4963 // catch edge case of _initComplete() firing after window.load() 4964 _windowLoaded = true; 4965 _event.remove(_win, 'load', _winOnLoad); 4966 }; 4967 4968 // sniff up-front 4969 _detectFlash(); 4970 4971 // focus and window load, init (primarily flash-driven) 4972 _event.add(_win, 'focus', _handleFocus); 4973 _event.add(_win, 'load', _handleFocus); 4974 _event.add(_win, 'load', _delayWaitForEI); 4975 _event.add(_win, 'load', _winOnLoad); 4976 4977 4978 if (_isSafari && _tryInitOnFocus) { 4979 // massive Safari 3.1 focus detection hack 4980 _event.add(_win, 'mousemove', _handleFocus); 4981 } 4982 4983 if (_doc.addEventListener) { 4984 4985 _doc.addEventListener('DOMContentLoaded', _domContentLoaded, false); 4986 4987 } else if (_doc.attachEvent) { 4988 4989 _doc.attachEvent('onreadystatechange', _domContentLoadedIE); 4990 4991 } else { 4992 4993 // no add/attachevent support - safe to assume no JS -> Flash either 4994 _debugTS('onload', false); 4995 _catchError({type:'NO_DOM2_EVENTS', fatal:true}); 4996 4997 } 4998 4999 if (_doc.readyState === 'complete') { 5000 // DOMReady has already happened. 5001 setTimeout(_domContentLoaded,100); 5002 } 5003 5004} // SoundManager() 5005 5006// SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading 5007 5008if (typeof SM2_DEFER === 'undefined' || !SM2_DEFER) { 5009 soundManager = new SoundManager(); 5010} 5011 5012/** 5013 * SoundManager public interfaces 5014 * ------------------------------ 5015 */ 5016 5017window.SoundManager = SoundManager; // constructor 5018window.soundManager = soundManager; // public API, flash callbacks etc. 5019 5020}(window));
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.