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