PageSourceSearch

https://www.radiojeans.net/cust/soundmanager2/soundmanager2.js

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

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