entityjs 0.3.0 → 0.3.1

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