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