PageSourceSearch

https://superlevel.rip/wp-content/themes/superlevel/assets/js/vendor/soundmanager2/soundmanager2.js

js superlevel.rip collected 2026-10-04 20:48:58 UTC 145,500 bytes, 5,555 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.