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