soundmanager-rails 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@
8
8
  * Code provided under the BSD License:
9
9
  * http://schillmania.com/projects/soundmanager2/license.txt
10
10
  *
11
- * V2.97a.20120318
11
+ * V2.97a.20120624
12
12
  */
13
13
 
14
14
  /*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio */
@@ -46,56 +46,33 @@ var soundManager = null;
46
46
 
47
47
  function SoundManager(smURL, smID) {
48
48
 
49
- // Top-level configuration options
50
-
51
- this.flashVersion = 8; // flash build to use (8 or 9.) Some API features require 9.
52
- this.debugMode = true; // enable debugging output (console.log() with HTML fallback)
53
- this.debugFlash = false; // enable debugging output inside SWF, troubleshoot Flash/browser issues
54
- this.useConsole = true; // use console.log() if available (otherwise, writes to #soundmanager-debug element)
55
- this.consoleOnly = true; // if console is being used, do not create/write to #soundmanager-debug
56
- this.waitForWindowLoad = false; // force SM2 to wait for window.onload() before trying to call soundManager.onload()
57
- this.bgColor = '#ffffff'; // SWF background color. N/A when wmode = 'transparent'
58
- this.useHighPerformance = false; // position:fixed flash movie can help increase js/flash speed, minimize lag
59
- this.flashPollingInterval = null; // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
60
- this.html5PollingInterval = null; // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
61
- this.flashLoadTimeout = 1000; // msec to wait for flash movie to load before failing (0 = infinity)
62
- this.wmode = null; // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
63
- this.allowScriptAccess = 'always'; // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
64
- this.useFlashBlock = false; // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
65
- this.useHTML5Audio = true; // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
66
- this.html5Test = /^(probably|maybe)$/i; // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
67
- 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.
68
- this.noSWFCache = false; // if true, appends ?ts={date} to break aggressive SWF caching.
69
-
70
- this.audioFormats = {
71
-
72
- /**
73
- * determines HTML5 support + flash requirements.
74
- * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
75
- * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
76
- * multiple MIME types may be tested while trying to get a positive canPlayType() response.
77
- */
78
-
79
- 'mp3': {
80
- 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
81
- 'required': true
82
- },
83
-
84
- 'mp4': {
85
- 'related': ['aac','m4a'], // additional formats under the MP4 container
86
- 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
87
- 'required': false
88
- },
49
+ /**
50
+ * soundManager configuration options list
51
+ * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)
52
+ * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})
53
+ */
89
54
 
90
- 'ogg': {
91
- 'type': ['audio/ogg; codecs=vorbis'],
92
- 'required': false
93
- },
94
-
95
- 'wav': {
96
- 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
97
- 'required': false
98
- }
55
+ this.setupOptions = {
56
+
57
+ 'url': (smURL || null), // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/
58
+ 'flashVersion': 8, // flash build to use (8 or 9.) Some API features require 9.
59
+ 'debugMode': true, // enable debugging output (console.log() with HTML fallback)
60
+ 'debugFlash': false, // enable debugging output inside SWF, troubleshoot Flash/browser issues
61
+ 'useConsole': true, // use console.log() if available (otherwise, writes to #soundmanager-debug element)
62
+ 'consoleOnly': true, // if console is being used, do not create/write to #soundmanager-debug
63
+ 'waitForWindowLoad': false, // force SM2 to wait for window.onload() before trying to call soundManager.onload()
64
+ 'bgColor': '#ffffff', // SWF background color. N/A when wmode = 'transparent'
65
+ 'useHighPerformance': false, // position:fixed flash movie can help increase js/flash speed, minimize lag
66
+ 'flashPollingInterval': null, // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
67
+ 'html5PollingInterval': null, // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
68
+ 'flashLoadTimeout': 1000, // msec to wait for flash movie to load before failing (0 = infinity)
69
+ 'wmode': null, // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
70
+ 'allowScriptAccess': 'always', // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
71
+ 'useFlashBlock': false, // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
72
+ 'useHTML5Audio': true, // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
73
+ 'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
74
+ 'preferFlash': true, // overrides useHTML5audio. if true and flash support present, will try to use flash for MP3/MP4 as needed since HTML5 audio support is still quirky in browsers.
75
+ 'noSWFCache': false // if true, appends ?ts={date} to break aggressive SWF caching.
99
76
 
100
77
  };
101
78
 
@@ -163,6 +140,38 @@ function SoundManager(smURL, smID) {
163
140
 
164
141
  };
165
142
 
143
+ this.audioFormats = {
144
+
145
+ /**
146
+ * determines HTML5 support + flash requirements.
147
+ * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
148
+ * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
149
+ * multiple MIME types may be tested while trying to get a positive canPlayType() response.
150
+ */
151
+
152
+ 'mp3': {
153
+ 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
154
+ 'required': true
155
+ },
156
+
157
+ 'mp4': {
158
+ 'related': ['aac','m4a'], // additional formats under the MP4 container
159
+ 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
160
+ 'required': false
161
+ },
162
+
163
+ 'ogg': {
164
+ 'type': ['audio/ogg; codecs=vorbis'],
165
+ 'required': false
166
+ },
167
+
168
+ 'wav': {
169
+ 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
170
+ 'required': false
171
+ }
172
+
173
+ };
174
+
166
175
  // HTML attributes (id + class names) for the SWF container
167
176
 
168
177
  this.movieID = 'sm2-container';
@@ -173,10 +182,9 @@ function SoundManager(smURL, smID) {
173
182
 
174
183
  // dynamic attributes
175
184
 
176
- this.versionNumber = 'V2.97a.20120318';
185
+ this.versionNumber = 'V2.97a.20120624';
177
186
  this.version = null;
178
187
  this.movieURL = null;
179
- this.url = (smURL || null);
180
188
  this.altURL = null;
181
189
  this.swfLoaded = false;
182
190
  this.enabled = false;
@@ -250,22 +258,26 @@ function SoundManager(smURL, smID) {
250
258
  'usingFlash': null // set if/when flash fallback is needed
251
259
  };
252
260
 
253
- this.flash = {}; // file type support hash
261
+ // file type support hash
262
+ this.flash = {};
254
263
 
255
- this.html5Only = false; // determined at init time
256
- this.ignoreFlash = false; // used for special cases (eg. iPad/iPhone/palm OS?)
264
+ // determined at init time
265
+ this.html5Only = false;
266
+
267
+ // used for special cases (eg. iPad/iPhone/palm OS?)
268
+ this.ignoreFlash = false;
257
269
 
258
270
  /**
259
271
  * a few private internals (OK, a lot. :D)
260
272
  */
261
273
 
262
274
  var SMSound,
263
- _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, _startTimer, _stopTimer, _timerExecute, _h5TimerCount = 0, _h5IntervalTimer = null, _parseURL,
275
+ _s = this, _flash = null, _sm = 'soundManager', _smc = _sm+'::', _h5 = 'HTML5::', _id, _ua = navigator.userAgent, _win = window, _wl = _win.location.href.toString(), _doc = document, _doNothing, _setProperties, _init, _fV, _on_queue = [], _debugOpen = true, _debugTS, _didAppend = false, _appendSuccess = false, _didInit = false, _disabled = false, _windowLoaded = false, _wDS, _wdCount = 0, _initComplete, _mixin, _assign, _extraOptions, _addOnEvent, _processOnEvents, _initUserOnload, _delayWaitForEI, _waitForEI, _setVersionInfo, _handleFocus, _strings, _initMovie, _domContentLoaded, _winOnLoad, _didDCLoaded, _getDocument, _createMovie, _catchError, _setPolling, _initDebug, _debugLevels = ['log', 'info', 'warn', 'error'], _defaultFlashVersion = 8, _disableObject, _failSafely, _normalizeMovieURL, _oRemoved = null, _oRemovedHTML = null, _str, _flashBlockHandler, _getSWFCSS, _swfCSS, _toggleDebug, _loopFix, _policyFix, _complain, _idCheck, _waitingForEI = false, _initPending = false, _startTimer, _stopTimer, _timerExecute, _h5TimerCount = 0, _h5IntervalTimer = null, _parseURL,
264
276
  _needsFlash = null, _featureCheck, _html5OK, _html5CanPlay, _html5Ext, _html5Unload, _domContentLoadedIE, _testHTML5, _event, _slice = Array.prototype.slice, _useGlobalHTML5Audio = false, _hasFlash, _detectFlash, _badSafariFix, _html5_events, _showSupport,
265
- _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)),
266
- _likesHTML5 = (_ua.match(/(mobile|pre\/|xoom)/i) || _is_iDevice),
277
+ _is_iDevice = _ua.match(/(ipad|iphone|ipod)/i), _isIE = _ua.match(/msie/i), _isWebkit = _ua.match(/webkit/i), _isSafari = (_ua.match(/safari/i) && !_ua.match(/chrome/i)), _isOpera = (_ua.match(/opera/i)),
278
+ _mobileHTML5 = (_ua.match(/(mobile|pre\/|xoom)/i) || _is_iDevice),
267
279
  _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
268
- _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,
280
+ _hasConsole = (typeof console !== 'undefined' && typeof console.log !== 'undefined'), _isFocused = (typeof _doc.hasFocus !== 'undefined'?_doc.hasFocus():null), _tryInitOnFocus = (_isSafari && (typeof _doc.hasFocus === 'undefined' || !_doc.hasFocus())), _okToDisable = !_tryInitOnFocus, _flashMIME = /(mp3|mp4|mpa|m4a)/i,
269
281
  _emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
270
282
  _overHTTP = (_doc.location?_doc.location.protocol.match(/http/i):null),
271
283
  _http = (!_overHTTP ? 'http:/'+'/' : ''),
@@ -279,6 +291,7 @@ function SoundManager(smURL, smID) {
279
291
 
280
292
  // use altURL if not "online"
281
293
  this.useAltURL = !_overHTTP;
294
+
282
295
  this._global_a = null;
283
296
 
284
297
  _swfCSS = {
@@ -295,7 +308,7 @@ function SoundManager(smURL, smID) {
295
308
 
296
309
  };
297
310
 
298
- if (_likesHTML5) {
311
+ if (_mobileHTML5) {
299
312
 
300
313
  // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
301
314
  _s.useHTML5Audio = true;
@@ -314,6 +327,29 @@ function SoundManager(smURL, smID) {
314
327
  * -----------------------
315
328
  */
316
329
 
330
+ /**
331
+ * Configures top-level soundManager properties.
332
+ *
333
+ * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
334
+ * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
335
+ */
336
+
337
+ this.setup = function(options) {
338
+
339
+ // warn if flash options have already been applied
340
+
341
+ if (typeof options !== 'undefined' && _didInit && _needsFlash && _s.ok() && (typeof options.flashVersion !== 'undefined' || typeof options.url !== 'undefined')) {
342
+ _complain(_str('setupLate'));
343
+ }
344
+
345
+ // TODO: defer: true?
346
+
347
+ _assign(options);
348
+
349
+ return _s;
350
+
351
+ };
352
+
317
353
  this.ok = function() {
318
354
 
319
355
  return (_needsFlash?(_didInit && !_disabled):(_s.useHTML5Audio && _s.hasHTML5));
@@ -332,14 +368,13 @@ function SoundManager(smURL, smID) {
332
368
  /**
333
369
  * Creates a SMSound sound object instance.
334
370
  *
335
- * @param {object} oOptions Sound options (at minimum, id and url are required.)
371
+ * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
336
372
  * @return {object} SMSound The new SMSound object.
337
373
  */
338
374
 
339
- this.createSound = function(oOptions) {
375
+ this.createSound = function(oOptions, _url) {
340
376
 
341
- var _cs, _cs_string,
342
- thisOptions = null, oSound = null, _tO = null;
377
+ var _cs, _cs_string, thisOptions = null, oSound = null, _tO = null;
343
378
 
344
379
  // <d>
345
380
  _cs = _sm+'.createSound(): ';
@@ -351,11 +386,11 @@ function SoundManager(smURL, smID) {
351
386
  return false;
352
387
  }
353
388
 
354
- if (arguments.length === 2) {
389
+ if (typeof _url !== 'undefined') {
355
390
  // function overloading in JS! :) ..assume simple createSound(id,url) use case
356
391
  oOptions = {
357
- 'id': arguments[0],
358
- 'url': arguments[1]
392
+ 'id': oOptions,
393
+ 'url': _url
359
394
  };
360
395
  }
361
396
 
@@ -392,7 +427,7 @@ function SoundManager(smURL, smID) {
392
427
  if (_html5OK(_tO)) {
393
428
 
394
429
  oSound = make();
395
- _s._wD('Loading sound '+_tO.id+' via HTML5');
430
+ _s._wD('Creating sound '+_tO.id+', using HTML5');
396
431
  oSound._setup_html5(_tO);
397
432
 
398
433
  } else {
@@ -400,24 +435,16 @@ function SoundManager(smURL, smID) {
400
435
  if (_fV > 8) {
401
436
  if (_tO.isMovieStar === null) {
402
437
  // attempt to detect MPEG-4 formats
403
- _tO.isMovieStar = (_tO.serverURL || (_tO.type ? _tO.type.match(_netStreamMimeTypes) : false) || _tO.url.match(_netStreamPattern));
438
+ _tO.isMovieStar = !!(_tO.serverURL || (_tO.type ? _tO.type.match(_netStreamMimeTypes) : false) || _tO.url.match(_netStreamPattern));
404
439
  }
405
440
  // <d>
406
441
  if (_tO.isMovieStar) {
407
442
  _s._wD(_cs + 'using MovieStar handling');
408
- }
409
- // </d>
410
- if (_tO.isMovieStar) {
411
- if (_tO.usePeakData) {
412
- _wDS('noPeak');
413
- _tO.usePeakData = false;
414
- }
415
- // <d>
416
443
  if (_tO.loops > 1) {
417
444
  _wDS('noNSLoop');
418
445
  }
419
- // </d>
420
446
  }
447
+ // </d>
421
448
  }
422
449
 
423
450
  _tO = _policyFix(_tO, _cs);
@@ -574,9 +601,11 @@ function SoundManager(smURL, smID) {
574
601
 
575
602
  this.play = function(sID, oOptions) {
576
603
 
604
+ var result = false;
605
+
577
606
  if (!_didInit || !_s.ok()) {
578
607
  _complain(_sm+'.play(): ' + _str(!_didInit?'notReady':'notOK'));
579
- return false;
608
+ return result;
580
609
  }
581
610
 
582
611
  if (!_idCheck(sID)) {
@@ -590,10 +619,9 @@ function SoundManager(smURL, smID) {
590
619
  // overloading use case, create+play: .play('someID',{url:'/path/to.mp3'});
591
620
  _s._wD(_sm+'.play(): attempting to create "' + sID + '"', 1);
592
621
  oOptions.id = sID;
593
- return _s.createSound(oOptions).play();
594
- } else {
595
- return false;
622
+ result = _s.createSound(oOptions).play();
596
623
  }
624
+ return result;
597
625
  }
598
626
 
599
627
  return _s.sounds[sID].play(oOptions);
@@ -930,14 +958,13 @@ function SoundManager(smURL, smID) {
930
958
  result = _html5CanPlay({type:sMIME});
931
959
  }
932
960
 
933
- if (!_needsFlash || result) {
934
- // no flash, or OK
935
- return result;
936
- } else {
961
+ if (!result && _needsFlash) {
937
962
  // if flash 9, test netStream (movieStar) types as well.
938
- return (sMIME && _s.ok() ? !!((_fV > 8 ? sMIME.match(_netStreamMimeTypes) : null) || sMIME.match(_s.mimePattern)) : null);
963
+ result = (sMIME && _s.ok() ? !!((_fV > 8 ? sMIME.match(_netStreamMimeTypes) : null) || sMIME.match(_s.mimePattern)) : null);
939
964
  }
940
965
 
966
+ return result;
967
+
941
968
  };
942
969
 
943
970
  /**
@@ -955,13 +982,12 @@ function SoundManager(smURL, smID) {
955
982
  result = _html5CanPlay({url: sURL});
956
983
  }
957
984
 
958
- if (!_needsFlash || result) {
959
- // no flash, or OK
960
- return result;
961
- } else {
962
- return (sURL && _s.ok() ? !!(sURL.match(_s.filePattern)) : null);
985
+ if (!result && _needsFlash) {
986
+ result = (sURL && _s.ok() ? !!(sURL.match(_s.filePattern)) : null);
963
987
  }
964
988
 
989
+ return result;
990
+
965
991
  };
966
992
 
967
993
  /**
@@ -1017,9 +1043,10 @@ function SoundManager(smURL, smID) {
1017
1043
 
1018
1044
  this.onready = function(oMethod, oScope) {
1019
1045
 
1020
- var sType = 'onready';
1046
+ var sType = 'onready',
1047
+ result = false;
1021
1048
 
1022
- if (oMethod && oMethod instanceof Function) {
1049
+ if (typeof oMethod === 'function') {
1023
1050
 
1024
1051
  // <d>
1025
1052
  if (_didInit) {
@@ -1034,7 +1061,7 @@ function SoundManager(smURL, smID) {
1034
1061
  _addOnEvent(sType, oMethod, oScope);
1035
1062
  _processOnEvents();
1036
1063
 
1037
- return true;
1064
+ result = true;
1038
1065
 
1039
1066
  } else {
1040
1067
 
@@ -1042,6 +1069,8 @@ function SoundManager(smURL, smID) {
1042
1069
 
1043
1070
  }
1044
1071
 
1072
+ return result;
1073
+
1045
1074
  };
1046
1075
 
1047
1076
  /**
@@ -1053,9 +1082,10 @@ function SoundManager(smURL, smID) {
1053
1082
 
1054
1083
  this.ontimeout = function(oMethod, oScope) {
1055
1084
 
1056
- var sType = 'ontimeout';
1085
+ var sType = 'ontimeout',
1086
+ result = false;
1057
1087
 
1058
- if (oMethod && oMethod instanceof Function) {
1088
+ if (typeof oMethod === 'function') {
1059
1089
 
1060
1090
  // <d>
1061
1091
  if (_didInit) {
@@ -1070,7 +1100,7 @@ function SoundManager(smURL, smID) {
1070
1100
  _addOnEvent(sType, oMethod, oScope);
1071
1101
  _processOnEvents({type:sType});
1072
1102
 
1073
- return true;
1103
+ result = true;
1074
1104
 
1075
1105
  } else {
1076
1106
 
@@ -1078,11 +1108,13 @@ function SoundManager(smURL, smID) {
1078
1108
 
1079
1109
  }
1080
1110
 
1111
+ return result;
1112
+
1081
1113
  };
1082
1114
 
1083
1115
  /**
1084
1116
  * Writes console.log()-style debug output to a console or in-browser element.
1085
- * Applies when SoundManager.debugMode = true
1117
+ * Applies when debugMode = true
1086
1118
  *
1087
1119
  * @param {string} sText The console message
1088
1120
  * @param {string} sType Optional: Log type of 'info', 'warn' or 'error'
@@ -1207,15 +1239,17 @@ function SoundManager(smURL, smID) {
1207
1239
 
1208
1240
  // trash ze flash
1209
1241
 
1210
- try {
1211
- if (_isIE) {
1212
- _oRemovedHTML = _flash.innerHTML;
1242
+ if (_flash) {
1243
+ try {
1244
+ if (_isIE) {
1245
+ _oRemovedHTML = _flash.innerHTML;
1246
+ }
1247
+ _oRemoved = _flash.parentNode.removeChild(_flash);
1248
+ _s._wD('Flash movie removed.');
1249
+ } catch(e) {
1250
+ // uh-oh.
1251
+ _wDS('badRemove', 2);
1213
1252
  }
1214
- _oRemoved = _flash.parentNode.removeChild(_flash);
1215
- _s._wD('Flash movie removed.');
1216
- } catch(e) {
1217
- // uh-oh.
1218
- _wDS('badRemove', 2);
1219
1253
  }
1220
1254
 
1221
1255
  // actually, force recreate of movie.
@@ -1247,6 +1281,7 @@ function SoundManager(smURL, smID) {
1247
1281
 
1248
1282
  this.getMoviePercent = function() {
1249
1283
 
1284
+ // interesting note: flash/ExternalInterface bridge methods are not typeof "function" nor instanceof Function, but are still valid.
1250
1285
  return (_flash && typeof _flash.PercentLoaded !== 'undefined' ? _flash.PercentLoaded() : null);
1251
1286
 
1252
1287
  };
@@ -1307,7 +1342,11 @@ function SoundManager(smURL, smID) {
1307
1342
  time: null
1308
1343
  };
1309
1344
 
1310
- this.sID = oOptions.id;
1345
+ this.id = oOptions.id;
1346
+
1347
+ // legacy
1348
+ this.sID = this.id;
1349
+
1311
1350
  this.url = oOptions.url;
1312
1351
  this.options = _mixin(oOptions);
1313
1352
 
@@ -1320,7 +1359,11 @@ function SoundManager(smURL, smID) {
1320
1359
  // assign property defaults
1321
1360
  this.pan = this.options.pan;
1322
1361
  this.volume = this.options.volume;
1362
+
1363
+ // whether or not this object is using HTML5
1323
1364
  this.isHTML5 = false;
1365
+
1366
+ // internal HTML5 Audio() object reference
1324
1367
  this._a = null;
1325
1368
 
1326
1369
  /**
@@ -1345,7 +1388,7 @@ function SoundManager(smURL, smID) {
1345
1388
 
1346
1389
  for (stuff in _t.options) {
1347
1390
  if (_t.options[stuff] !== null) {
1348
- if (_t.options[stuff] instanceof Function) {
1391
+ if (typeof _t.options[stuff] === 'function') {
1349
1392
  // handle functions specially
1350
1393
  sF = _t.options[stuff].toString();
1351
1394
  // normalize spaces
@@ -1416,9 +1459,13 @@ function SoundManager(smURL, smID) {
1416
1459
  _iO = _t._iO;
1417
1460
 
1418
1461
  _lastURL = _t.url;
1462
+
1463
+ // reset a few state properties
1464
+
1419
1465
  _t.loaded = false;
1420
1466
  _t.readyState = 1;
1421
1467
  _t.playState = 0;
1468
+ _t.id3 = {};
1422
1469
 
1423
1470
  // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
1424
1471
 
@@ -1428,17 +1475,35 @@ function SoundManager(smURL, smID) {
1428
1475
 
1429
1476
  if (!oS._called_load) {
1430
1477
 
1431
- _s._wD(_h5+'load: '+_t.sID);
1478
+ _s._wD(_h5+'load: '+_t.id);
1479
+
1432
1480
  _t._html5_canplay = false;
1433
1481
 
1434
- // given explicit load call, try to get whole file.
1482
+ // TODO: review called_load / html5_canplay logic
1483
+
1484
+ // if url provided directly to load(), assign it here.
1485
+
1486
+ if (_t._a.src !== _iO.url) {
1487
+
1488
+ _s._wD(_wDS('manURL') + ': ' + _iO.url);
1489
+
1490
+ _t._a.src = _iO.url;
1491
+
1492
+ // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
1493
+
1494
+ // reset position for new URL
1495
+ _t.setPosition(0);
1496
+
1497
+ }
1498
+
1499
+ // given explicit load call, try to preload.
1500
+
1435
1501
  // early HTML5 implementation (non-standard)
1436
1502
  _t._a.autobuffer = 'auto';
1437
1503
 
1438
1504
  // standard
1439
1505
  _t._a.preload = 'auto';
1440
1506
 
1441
- oS.load();
1442
1507
  oS._called_load = true;
1443
1508
 
1444
1509
  if (_iO.autoPlay) {
@@ -1446,7 +1511,9 @@ function SoundManager(smURL, smID) {
1446
1511
  }
1447
1512
 
1448
1513
  } else {
1449
- _s._wD(_h5+'ignoring request to load again: '+_t.sID);
1514
+
1515
+ _s._wD(_h5+'ignoring request to load again: '+_t.id);
1516
+
1450
1517
  }
1451
1518
 
1452
1519
  } else {
@@ -1457,9 +1524,9 @@ function SoundManager(smURL, smID) {
1457
1524
  // re-assign local shortcut
1458
1525
  _iO = _t._iO;
1459
1526
  if (_fV === 8) {
1460
- _flash._load(_t.sID, _iO.url, _iO.stream, _iO.autoPlay, (_iO.whileloading?1:0), _iO.loops||1, _iO.usePolicyFile);
1527
+ _flash._load(_t.id, _iO.url, _iO.stream, _iO.autoPlay, (_iO.whileloading?1:0), _iO.loops||1, _iO.usePolicyFile);
1461
1528
  } else {
1462
- _flash._load(_t.sID, _iO.url, !!(_iO.stream), !!(_iO.autoPlay), _iO.loops||1, !!(_iO.autoLoad), _iO.usePolicyFile);
1529
+ _flash._load(_t.id, _iO.url, !!(_iO.stream), !!(_iO.autoPlay), _iO.loops||1, !!(_iO.autoLoad), _iO.usePolicyFile);
1463
1530
  }
1464
1531
  } catch(e) {
1465
1532
  _wDS('smError', 2);
@@ -1488,20 +1555,30 @@ function SoundManager(smURL, smID) {
1488
1555
 
1489
1556
  if (_t.readyState !== 0) {
1490
1557
 
1491
- _s._wD('SMSound.unload(): "' + _t.sID + '"');
1558
+ _s._wD('SMSound.unload(): "' + _t.id + '"');
1492
1559
 
1493
1560
  if (!_t.isHTML5) {
1561
+
1494
1562
  if (_fV === 8) {
1495
- _flash._unload(_t.sID, _emptyURL);
1563
+ _flash._unload(_t.id, _emptyURL);
1496
1564
  } else {
1497
- _flash._unload(_t.sID);
1565
+ _flash._unload(_t.id);
1498
1566
  }
1567
+
1499
1568
  } else {
1569
+
1500
1570
  _stop_html5_timer();
1571
+
1501
1572
  if (_t._a) {
1573
+
1502
1574
  _t._a.pause();
1503
- _html5Unload(_t._a);
1575
+ _html5Unload(_t._a, _emptyURL);
1576
+
1577
+ // reset local URL for next load / play call, too
1578
+ _t.url = _emptyURL;
1579
+
1504
1580
  }
1581
+
1505
1582
  }
1506
1583
 
1507
1584
  // reset load/status flags
@@ -1519,14 +1596,14 @@ function SoundManager(smURL, smID) {
1519
1596
 
1520
1597
  this.destruct = function(_bFromSM) {
1521
1598
 
1522
- _s._wD('SMSound.destruct(): "' + _t.sID + '"');
1599
+ _s._wD('SMSound.destruct(): "' + _t.id + '"');
1523
1600
 
1524
1601
  if (!_t.isHTML5) {
1525
1602
 
1526
1603
  // kill sound within Flash
1527
1604
  // Disable the onfailure handler
1528
1605
  _t._iO.onfailure = null;
1529
- _flash._destroySound(_t.sID);
1606
+ _flash._destroySound(_t.id);
1530
1607
 
1531
1608
  } else {
1532
1609
 
@@ -1547,7 +1624,7 @@ function SoundManager(smURL, smID) {
1547
1624
 
1548
1625
  if (!_bFromSM) {
1549
1626
  // ensure deletion from controller
1550
- _s.destroySound(_t.sID, true);
1627
+ _s.destroySound(_t.id, true);
1551
1628
 
1552
1629
  }
1553
1630
 
@@ -1562,13 +1639,15 @@ function SoundManager(smURL, smID) {
1562
1639
 
1563
1640
  this.play = function(oOptions, _updatePlayState) {
1564
1641
 
1565
- var fN, allowMulti, a, onready;
1642
+ var fN, allowMulti, a, onready, startOK = true,
1643
+ exit = null;
1566
1644
 
1567
1645
  // <d>
1568
1646
  fN = 'SMSound.play(): ';
1569
1647
  // </d>
1570
1648
 
1571
- _updatePlayState = _updatePlayState === undefined ? true : _updatePlayState; // default to true
1649
+ // default to true
1650
+ _updatePlayState = (typeof _updatePlayState === 'undefined' ? true : _updatePlayState);
1572
1651
 
1573
1652
  if (!oOptions) {
1574
1653
  oOptions = {};
@@ -1597,47 +1676,57 @@ function SoundManager(smURL, smID) {
1597
1676
  if (_t.playState === 1 && !_t.paused) {
1598
1677
  allowMulti = _t._iO.multiShot;
1599
1678
  if (!allowMulti) {
1600
- _s._wD(fN + '"' + _t.sID + '" already playing (one-shot)', 1);
1601
- return _t;
1679
+ _s._wD(fN + '"' + _t.id + '" already playing (one-shot)', 1);
1680
+ exit = _t;
1602
1681
  } else {
1603
- _s._wD(fN + '"' + _t.sID + '" already playing (multi-shot)', 1);
1682
+ _s._wD(fN + '"' + _t.id + '" already playing (multi-shot)', 1);
1604
1683
  }
1605
1684
  }
1606
1685
 
1686
+ if (exit !== null) {
1687
+ return exit;
1688
+ }
1689
+
1607
1690
  if (!_t.loaded) {
1608
1691
 
1609
1692
  if (_t.readyState === 0) {
1610
1693
 
1611
- _s._wD(fN + 'Attempting to load "' + _t.sID + '"', 1);
1694
+ _s._wD(fN + 'Attempting to load "' + _t.id + '"', 1);
1612
1695
 
1613
1696
  // try to get this sound playing ASAP
1614
1697
  if (!_t.isHTML5) {
1615
1698
  // assign directly because setAutoPlay() increments the instanceCount
1616
1699
  _t._iO.autoPlay = true;
1700
+ _t.load(_t._iO);
1701
+ } else {
1702
+ // iOS needs this when recycling sounds, loading a new URL on an existing object.
1703
+ _t.load(_t._iO);
1617
1704
  }
1618
1705
 
1619
- _t.load(_t._iO);
1620
-
1621
1706
  } else if (_t.readyState === 2) {
1622
1707
 
1623
- _s._wD(fN + 'Could not load "' + _t.sID + '" - exiting', 2);
1624
- return _t;
1708
+ _s._wD(fN + 'Could not load "' + _t.id + '" - exiting', 2);
1709
+ exit = _t;
1625
1710
 
1626
1711
  } else {
1627
1712
 
1628
- _s._wD(fN + '"' + _t.sID + '" is loading - attempting to play..', 1);
1713
+ _s._wD(fN + '"' + _t.id + '" is loading - attempting to play..', 1);
1629
1714
 
1630
1715
  }
1631
1716
 
1632
1717
  } else {
1633
1718
 
1634
- _s._wD(fN + '"' + _t.sID + '"');
1719
+ _s._wD(fN + '"' + _t.id + '"');
1635
1720
 
1636
1721
  }
1637
1722
 
1723
+ if (exit !== null) {
1724
+ return exit;
1725
+ }
1726
+
1638
1727
  if (!_t.isHTML5 && _fV === 9 && _t.position > 0 && _t.position === _t.duration) {
1639
1728
  // flash 9 needs a position reset if play() is called while at the end of a sound.
1640
- _s._wD(fN + '"' + _t.sID + '": Sound at end, resetting to position:0');
1729
+ _s._wD(fN + '"' + _t.id + '": Sound at end, resetting to position:0');
1641
1730
  oOptions.position = 0;
1642
1731
  }
1643
1732
 
@@ -1652,7 +1741,7 @@ function SoundManager(smURL, smID) {
1652
1741
  if (_t.paused && _t.position && _t.position > 0) {
1653
1742
 
1654
1743
  // https://gist.github.com/37b17df75cc4d7a90bf6
1655
- _s._wD(fN + '"' + _t.sID + '" is resuming from paused state',1);
1744
+ _s._wD(fN + '"' + _t.id + '" is resuming from paused state',1);
1656
1745
  _t.resume();
1657
1746
 
1658
1747
  } else {
@@ -1673,26 +1762,30 @@ function SoundManager(smURL, smID) {
1673
1762
  if (_t.isHTML5 && !_t._html5_canplay) {
1674
1763
 
1675
1764
  // this hasn't been loaded yet. load it first, and then do this again.
1676
- _s._wD(fN+'Beginning load of "'+ _t.sID+'" for from/to case');
1765
+ _s._wD(fN+'Beginning load of "'+ _t.id+'" for from/to case');
1677
1766
 
1678
1767
  _t.load({
1679
1768
  _oncanplay: onready
1680
1769
  });
1681
1770
 
1682
- return false;
1771
+ exit = false;
1683
1772
 
1684
1773
  } else if (!_t.isHTML5 && !_t.loaded && (!_t.readyState || _t.readyState !== 2)) {
1685
1774
 
1686
1775
  // to be safe, preload the whole thing in Flash.
1687
1776
 
1688
- _s._wD(fN+'Preloading "'+ _t.sID+'" for from/to case');
1777
+ _s._wD(fN+'Preloading "'+ _t.id+'" for from/to case');
1689
1778
 
1690
1779
  _t.load({
1691
1780
  onload: onready
1692
1781
  });
1693
1782
 
1694
- return false;
1783
+ exit = false;
1784
+
1785
+ }
1695
1786
 
1787
+ if (exit !== null) {
1788
+ return exit;
1696
1789
  }
1697
1790
 
1698
1791
  // otherwise, we're ready to go. re-apply local options, and continue
@@ -1701,14 +1794,14 @@ function SoundManager(smURL, smID) {
1701
1794
 
1702
1795
  }
1703
1796
 
1704
- _s._wD(fN+'"'+ _t.sID+'" is starting to play');
1797
+ _s._wD(fN+'"'+ _t.id+'" is starting to play');
1705
1798
 
1706
1799
  if (!_t.instanceCount || _t._iO.multiShotEvents || (!_t.isHTML5 && _fV > 8 && !_t.getAutoPlay())) {
1707
1800
  _t.instanceCount++;
1708
1801
  }
1709
1802
 
1710
1803
  // if first play and onposition parameters exist, apply them now
1711
- if (_t.playState === 0 && _t._iO.onposition) {
1804
+ if (_t._iO.onposition && _t.playState === 0) {
1712
1805
  _attachOnPosition(_t);
1713
1806
  }
1714
1807
 
@@ -1731,13 +1824,27 @@ function SoundManager(smURL, smID) {
1731
1824
 
1732
1825
  if (!_t.isHTML5) {
1733
1826
 
1734
- _flash._start(_t.sID, _t._iO.loops || 1, (_fV === 9?_t._iO.position:_t._iO.position / 1000));
1827
+ startOK = _flash._start(_t.id, _t._iO.loops || 1, (_fV === 9 ? _t._iO.position : _t._iO.position / 1000), _t._iO.multiShot);
1828
+
1829
+ if (_fV === 9 && !startOK) {
1830
+ // edge case: no sound hardware, or 32-channel flash ceiling hit.
1831
+ // applies only to Flash 9, non-NetStream/MovieStar sounds.
1832
+ // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
1833
+ _s._wD(fN+ _t.id+': No sound hardware, or 32-sound ceiling hit');
1834
+ if (_t._iO.onplayerror) {
1835
+ _t._iO.onplayerror.apply(_t);
1836
+ }
1837
+
1838
+ }
1735
1839
 
1736
1840
  } else {
1737
1841
 
1738
1842
  _start_html5_timer();
1843
+
1739
1844
  a = _t._setup_html5();
1845
+
1740
1846
  _t.setPosition(_t._iO.position);
1847
+
1741
1848
  a.play();
1742
1849
 
1743
1850
  }
@@ -1782,7 +1889,7 @@ function SoundManager(smURL, smID) {
1782
1889
 
1783
1890
  if (!_t.isHTML5) {
1784
1891
 
1785
- _flash._stop(_t.sID, bAll);
1892
+ _flash._stop(_t.id, bAll);
1786
1893
 
1787
1894
  // hack for netStream: just unload
1788
1895
  if (_iO.serverURL) {
@@ -1837,16 +1944,16 @@ function SoundManager(smURL, smID) {
1837
1944
 
1838
1945
  this.setAutoPlay = function(autoPlay) {
1839
1946
 
1840
- _s._wD('sound '+_t.sID+' turned autoplay ' + (autoPlay ? 'on' : 'off'));
1947
+ _s._wD('sound '+_t.id+' turned autoplay ' + (autoPlay ? 'on' : 'off'));
1841
1948
  _t._iO.autoPlay = autoPlay;
1842
1949
 
1843
1950
  if (!_t.isHTML5) {
1844
- _flash._setAutoPlay(_t.sID, autoPlay);
1951
+ _flash._setAutoPlay(_t.id, autoPlay);
1845
1952
  if (autoPlay) {
1846
1953
  // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
1847
1954
  if (!_t.instanceCount && _t.readyState === 1) {
1848
1955
  _t.instanceCount++;
1849
- _s._wD('sound '+_t.sID+' incremented instance count to '+_t.instanceCount);
1956
+ _s._wD('sound '+_t.id+' incremented instance count to '+_t.instanceCount);
1850
1957
  }
1851
1958
  }
1852
1959
  }
@@ -1874,7 +1981,7 @@ function SoundManager(smURL, smID) {
1874
1981
 
1875
1982
  this.setPosition = function(nMsecOffset) {
1876
1983
 
1877
- if (nMsecOffset === undefined) {
1984
+ if (typeof nMsecOffset === 'undefined') {
1878
1985
  nMsecOffset = 0;
1879
1986
  }
1880
1987
 
@@ -1895,7 +2002,7 @@ function SoundManager(smURL, smID) {
1895
2002
  position = (_fV === 9 ? _t.position : position1K);
1896
2003
  if (_t.readyState && _t.readyState !== 2) {
1897
2004
  // if paused or not playing, will not resume (by playing)
1898
- _flash._setPosition(_t.sID, position, (_t.paused || !_t.playState));
2005
+ _flash._setPosition(_t.id, position, (_t.paused || !_t.playState), _t._iO.multiShot);
1899
2006
  }
1900
2007
 
1901
2008
  } else if (_t._a) {
@@ -1953,8 +2060,8 @@ function SoundManager(smURL, smID) {
1953
2060
  _t.paused = true;
1954
2061
 
1955
2062
  if (!_t.isHTML5) {
1956
- if (_bCallFlash || _bCallFlash === undefined) {
1957
- _flash._pause(_t.sID);
2063
+ if (_bCallFlash || typeof _bCallFlash === 'undefined') {
2064
+ _flash._pause(_t.id, _t._iO.multiShot);
1958
2065
  }
1959
2066
  } else {
1960
2067
  _t._setup_html5().pause();
@@ -2001,7 +2108,7 @@ function SoundManager(smURL, smID) {
2001
2108
  _t.setPosition(_t.position);
2002
2109
  }
2003
2110
  // flash method is toggle-based (pause/resume)
2004
- _flash._pause(_t.sID);
2111
+ _flash._pause(_t.id, _iO.multiShot);
2005
2112
  } else {
2006
2113
  _t._setup_html5().play();
2007
2114
  _start_html5_timer();
@@ -2063,7 +2170,7 @@ function SoundManager(smURL, smID) {
2063
2170
  }
2064
2171
 
2065
2172
  if (!_t.isHTML5) {
2066
- _flash._setPan(_t.sID, nPan);
2173
+ _flash._setPan(_t.id, nPan);
2067
2174
  } // else { no HTML5 pan? }
2068
2175
 
2069
2176
  _t._iO.pan = nPan;
@@ -2102,7 +2209,7 @@ function SoundManager(smURL, smID) {
2102
2209
  }
2103
2210
 
2104
2211
  if (!_t.isHTML5) {
2105
- _flash._setVolume(_t.sID, (_s.muted && !_t.muted) || _t.muted?0:nVol);
2212
+ _flash._setVolume(_t.id, (_s.muted && !_t.muted) || _t.muted?0:nVol);
2106
2213
  } else if (_t._a) {
2107
2214
  // valid range: 0-1
2108
2215
  _t._a.volume = Math.max(0, Math.min(1, nVol/100));
@@ -2130,7 +2237,7 @@ function SoundManager(smURL, smID) {
2130
2237
  _t.muted = true;
2131
2238
 
2132
2239
  if (!_t.isHTML5) {
2133
- _flash._setVolume(_t.sID, 0);
2240
+ _flash._setVolume(_t.id, 0);
2134
2241
  } else if (_t._a) {
2135
2242
  _t._a.muted = true;
2136
2243
  }
@@ -2148,10 +2255,10 @@ function SoundManager(smURL, smID) {
2148
2255
  this.unmute = function() {
2149
2256
 
2150
2257
  _t.muted = false;
2151
- var hasIO = typeof _t._iO.volume !== 'undefined';
2258
+ var hasIO = (typeof _t._iO.volume !== 'undefined');
2152
2259
 
2153
2260
  if (!_t.isHTML5) {
2154
- _flash._setVolume(_t.sID, hasIO?_t._iO.volume:_t.options.volume);
2261
+ _flash._setVolume(_t.id, hasIO?_t._iO.volume:_t.options.volume);
2155
2262
  } else if (_t._a) {
2156
2263
  _t._a.muted = false;
2157
2264
  }
@@ -2292,7 +2399,7 @@ function SoundManager(smURL, smID) {
2292
2399
  end = function() {
2293
2400
 
2294
2401
  // end has been reached.
2295
- _s._wD(_t.sID + ': "to" time of ' + t + ' reached.');
2402
+ _s._wD(_t.id + ': "to" time of ' + t + ' reached.');
2296
2403
 
2297
2404
  // detach listener
2298
2405
  _t.clearOnPosition(t, end);
@@ -2304,7 +2411,7 @@ function SoundManager(smURL, smID) {
2304
2411
 
2305
2412
  start = function() {
2306
2413
 
2307
- _s._wD(_t.sID + ': playing "from" ' + f);
2414
+ _s._wD(_t.id + ': playing "from" ' + f);
2308
2415
 
2309
2416
  // add listener for end
2310
2417
  if (t !== null && !isNaN(t)) {
@@ -2384,10 +2491,13 @@ function SoundManager(smURL, smID) {
2384
2491
 
2385
2492
  };
2386
2493
 
2387
- _resetProperties = function() {
2494
+ _resetProperties = function(retainPosition) {
2495
+
2496
+ if (!retainPosition) {
2497
+ _onPositionItems = [];
2498
+ _onPositionFired = 0;
2499
+ }
2388
2500
 
2389
- _onPositionItems = [];
2390
- _onPositionFired = 0;
2391
2501
  _onplay_called = false;
2392
2502
 
2393
2503
  _t._hasTimer = null;
@@ -2397,6 +2507,7 @@ function SoundManager(smURL, smID) {
2397
2507
  _t.bytesTotal = null;
2398
2508
  _t.duration = (_t._iO && _t._iO.duration ? _t._iO.duration : null);
2399
2509
  _t.durationEstimate = null;
2510
+ _t.buffered = [];
2400
2511
 
2401
2512
  // legacy: 1D array
2402
2513
  _t.eqData = [];
@@ -2430,6 +2541,8 @@ function SoundManager(smURL, smID) {
2430
2541
  _t.playState = 0;
2431
2542
  _t.position = null;
2432
2543
 
2544
+ _t.id3 = {};
2545
+
2433
2546
  };
2434
2547
 
2435
2548
  _resetProperties();
@@ -2483,15 +2596,15 @@ function SoundManager(smURL, smID) {
2483
2596
 
2484
2597
  }
2485
2598
 
2486
- return isNew;
2487
-
2488
- } else {
2599
+ }/* else {
2489
2600
 
2490
- // _s._wD('_onTimer: Warn for "'+_t.sID+'": '+(!_t._a?'Could not find element. ':'')+(_t.playState === 0?'playState bad, 0?':'playState = '+_t.playState+', OK'));
2601
+ // _s._wD('_onTimer: Warn for "'+_t.id+'": '+(!_t._a?'Could not find element. ':'')+(_t.playState === 0?'playState bad, 0?':'playState = '+_t.playState+', OK'));
2491
2602
 
2492
2603
  return false;
2493
2604
 
2494
- }
2605
+ }*/
2606
+
2607
+ return isNew;
2495
2608
 
2496
2609
  }
2497
2610
 
@@ -2507,28 +2620,57 @@ function SoundManager(smURL, smID) {
2507
2620
 
2508
2621
  };
2509
2622
 
2623
+ this._apply_loop = function(a, nLoops) {
2624
+
2625
+ /**
2626
+ * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
2627
+ * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
2628
+ */
2629
+
2630
+ // <d>
2631
+ if (!a.loop && nLoops > 1) {
2632
+ _s._wD('Note: Native HTML5 looping is infinite.');
2633
+ }
2634
+ // </d>
2635
+
2636
+ a.loop = (nLoops > 1 ? 'loop' : '');
2637
+
2638
+ };
2639
+
2510
2640
  this._setup_html5 = function(oOptions) {
2511
2641
 
2512
2642
  var _iO = _mixin(_t._iO, oOptions), d = decodeURI,
2513
2643
  _a = _useGlobalHTML5Audio ? _s._global_a : _t._a,
2514
2644
  _dURL = d(_iO.url),
2515
- _oldIO = (_a && _a._t ? _a._t.instanceOptions : null);
2645
+ _oldIO = (_a && _a._t ? _a._t.instanceOptions : null),
2646
+ result;
2516
2647
 
2517
2648
  if (_a) {
2518
2649
 
2519
2650
  if (_a._t) {
2520
2651
 
2521
2652
  if (!_useGlobalHTML5Audio && _dURL === d(_lastURL)) {
2653
+
2522
2654
  // same url, ignore request
2523
- return _a;
2655
+ result = _a;
2656
+
2524
2657
  } else if (_useGlobalHTML5Audio && _oldIO.url === _iO.url && (!_lastURL || (_lastURL === _oldIO.url))) {
2658
+
2525
2659
  // iOS-type reuse case
2526
- return _a;
2660
+ result = _a;
2661
+
2662
+ }
2663
+
2664
+ if (result) {
2665
+
2666
+ _t._apply_loop(_a, _iO.loops);
2667
+ return result;
2668
+
2527
2669
  }
2528
2670
 
2529
2671
  }
2530
2672
 
2531
- _s._wD('setting new URL on existing object: ' + _dURL + (_lastURL ? ', old URL: ' + _lastURL : ''));
2673
+ _s._wD('setting URL on existing object: ' + _dURL + (_lastURL ? ', old URL: ' + _lastURL : ''));
2532
2674
 
2533
2675
  /**
2534
2676
  * "First things first, I, Poppa.." (reset the previous state of the old sound, if playing)
@@ -2537,11 +2679,14 @@ function SoundManager(smURL, smID) {
2537
2679
  */
2538
2680
 
2539
2681
  if (_useGlobalHTML5Audio && _a._t && _a._t.playState && _iO.url !== _oldIO.url) {
2682
+
2540
2683
  _a._t.stop();
2684
+
2541
2685
  }
2542
2686
 
2543
- // new URL, so reset load/playstate and so on
2544
- _resetProperties();
2687
+ // reset load/playstate, onPosition etc. if the URL is new.
2688
+ // somewhat-tricky object re-use vs. new SMSound object, old vs. new URL comparisons
2689
+ _resetProperties((_oldIO && _oldIO.url ? _iO.url === _oldIO.url : (_lastURL ? _lastURL === _iO.url : false)));
2545
2690
 
2546
2691
  _a.src = _iO.url;
2547
2692
  _t.url = _iO.url;
@@ -2550,18 +2695,28 @@ function SoundManager(smURL, smID) {
2550
2695
 
2551
2696
  } else {
2552
2697
 
2553
- _s._wD('creating HTML5 Audio() element with URL: '+_dURL);
2554
- _a = new Audio(_iO.url);
2698
+ _wDS('h5a');
2555
2699
 
2556
- _a._called_load = false;
2700
+ if (_iO.autoLoad || _iO.autoPlay) {
2701
+
2702
+ _t._a = new Audio(_iO.url);
2703
+
2704
+ } else {
2705
+
2706
+ // null for stupid Opera 9.64 case
2707
+ _t._a = (_isOpera ? new Audio(null) : new Audio());
2557
2708
 
2558
- // android (seen in 2.3/Honeycomb) sometimes fails first .load() -> .play(), results in playback failure and ended() events?
2559
- if (_is_android) {
2560
- _a._called_load = true;
2561
2709
  }
2562
2710
 
2711
+ // assign local reference
2712
+ _a = _t._a;
2713
+
2714
+ _a._called_load = false;
2715
+
2563
2716
  if (_useGlobalHTML5Audio) {
2717
+
2564
2718
  _s._global_a = _a;
2719
+
2565
2720
  }
2566
2721
 
2567
2722
  }
@@ -2575,7 +2730,8 @@ function SoundManager(smURL, smID) {
2575
2730
  _a._t = _t;
2576
2731
 
2577
2732
  _add_html5_events();
2578
- _a.loop = (_iO.loops>1?'loop':'');
2733
+
2734
+ _t._apply_loop(_a, _iO.loops);
2579
2735
 
2580
2736
  if (_iO.autoLoad || _iO.autoPlay) {
2581
2737
 
@@ -2586,14 +2742,11 @@ function SoundManager(smURL, smID) {
2586
2742
  // early HTML5 implementation (non-standard)
2587
2743
  _a.autobuffer = false;
2588
2744
 
2589
- // standard
2590
- _a.preload = 'none';
2745
+ // standard ('none' is also an option.)
2746
+ _a.preload = 'auto';
2591
2747
 
2592
2748
  }
2593
2749
 
2594
- // boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
2595
- _a.loop = (_iO.loops > 1 ? 'loop' : '');
2596
-
2597
2750
  return _a;
2598
2751
 
2599
2752
  };
@@ -2610,7 +2763,6 @@ function SoundManager(smURL, smID) {
2610
2763
  return _t._a ? _t._a.addEventListener(oEvt, oFn, bCapture||false) : null;
2611
2764
  }
2612
2765
 
2613
- _s._wD(_h5+'adding event listeners: '+_t.sID);
2614
2766
  _t._a._added_events = true;
2615
2767
 
2616
2768
  for (f in _html5_events) {
@@ -2633,7 +2785,7 @@ function SoundManager(smURL, smID) {
2633
2785
  return (_t._a ? _t._a.removeEventListener(oEvt, oFn, bCapture||false) : null);
2634
2786
  }
2635
2787
 
2636
- _s._wD(_h5+'removing event listeners: '+_t.sID);
2788
+ _s._wD(_h5+'removing event listeners: '+_t.id);
2637
2789
  _t._a._added_events = false;
2638
2790
 
2639
2791
  for (f in _html5_events) {
@@ -2652,11 +2804,13 @@ function SoundManager(smURL, smID) {
2652
2804
  this._onload = function(nSuccess) {
2653
2805
 
2654
2806
 
2655
- var fN, loadOK = !!(nSuccess);
2807
+ var fN,
2808
+ // check for duration to prevent false positives from flash 8 when loading from cache.
2809
+ loadOK = (!!(nSuccess) || (!_t.isHTML5 && _fV === 8 && _t.duration));
2656
2810
 
2657
2811
  // <d>
2658
2812
  fN = 'SMSound._onload(): ';
2659
- _s._wD(fN + '"' + _t.sID + '"' + (loadOK?' loaded.':' failed to load? - ' + _t.url), (loadOK?1:2));
2813
+ _s._wD(fN + '"' + _t.id + '"' + (loadOK?' loaded.':' failed to load? - ' + _t.url), (loadOK?1:2));
2660
2814
  if (!loadOK && !_t.isHTML5) {
2661
2815
  if (_s.sandbox.noRemote === true) {
2662
2816
  _s._wD(fN + _str('noNet'), 1);
@@ -2724,7 +2878,7 @@ function SoundManager(smURL, smID) {
2724
2878
  this._onfailure = function(msg, level, code) {
2725
2879
 
2726
2880
  _t.failures++;
2727
- _s._wD('SMSound._onfailure(): "'+_t.sID+'" count '+_t.failures);
2881
+ _s._wD('SMSound._onfailure(): "'+_t.id+'" count '+_t.failures);
2728
2882
 
2729
2883
  if (_t._iO.onfailure && _t.failures === 1) {
2730
2884
  _t._iO.onfailure(_t, msg, level, code);
@@ -2760,12 +2914,17 @@ function SoundManager(smURL, smID) {
2760
2914
  _t._iO = {};
2761
2915
  _stop_html5_timer();
2762
2916
 
2917
+ // reset position, too
2918
+ if (_t.isHTML5) {
2919
+ _t.position = 0;
2920
+ }
2921
+
2763
2922
  }
2764
2923
 
2765
2924
  if (!_t.instanceCount || _t._iO.multiShotEvents) {
2766
2925
  // fire onfinish for last, or every instance
2767
2926
  if (_io_onfinish) {
2768
- _s._wD('SMSound._onfinish(): "' + _t.sID + '"');
2927
+ _s._wD('SMSound._onfinish(): "' + _t.id + '"');
2769
2928
  _io_onfinish.apply(_t);
2770
2929
  }
2771
2930
  }
@@ -2786,30 +2945,35 @@ function SoundManager(smURL, smID) {
2786
2945
  if (!_iO.isMovieStar) {
2787
2946
 
2788
2947
  if (_iO.duration) {
2789
- // use options, if specified and larger
2948
+ // use duration from options, if specified and larger
2790
2949
  _t.durationEstimate = (_t.duration > _iO.duration) ? _t.duration : _iO.duration;
2791
2950
  } else {
2792
2951
  _t.durationEstimate = parseInt((_t.bytesTotal / _t.bytesLoaded) * _t.duration, 10);
2793
-
2794
2952
  }
2795
2953
 
2796
- if (_t.durationEstimate === undefined) {
2954
+ if (typeof _t.durationEstimate === 'undefined') {
2797
2955
  _t.durationEstimate = _t.duration;
2798
2956
  }
2799
2957
 
2800
- if (_t.readyState !== 3 && _iO.whileloading) {
2801
- _iO.whileloading.apply(_t);
2802
- }
2803
-
2804
2958
  } else {
2805
2959
 
2806
2960
  _t.durationEstimate = _t.duration;
2807
- if (_t.readyState !== 3 && _iO.whileloading) {
2808
- _iO.whileloading.apply(_t);
2809
- }
2810
2961
 
2811
2962
  }
2812
2963
 
2964
+ // for flash, reflect sequential-load-style buffering
2965
+ if (!_t.isHTML5) {
2966
+ _t.buffered = [{
2967
+ 'start': 0,
2968
+ 'end': _t.duration
2969
+ }];
2970
+ }
2971
+
2972
+ // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
2973
+ if ((_t.readyState !== 3 || _t.isHTML5) && _iO.whileloading) {
2974
+ _iO.whileloading.apply(_t);
2975
+ }
2976
+
2813
2977
  };
2814
2978
 
2815
2979
  this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {
@@ -2822,7 +2986,9 @@ function SoundManager(smURL, smID) {
2822
2986
  return false;
2823
2987
  }
2824
2988
 
2825
- _t.position = nPosition;
2989
+ // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.
2990
+ _t.position = Math.max(0, nPosition);
2991
+
2826
2992
  _t._processOnPosition();
2827
2993
 
2828
2994
  if (!_t.isHTML5 && _fV > 8) {
@@ -2872,6 +3038,24 @@ function SoundManager(smURL, smID) {
2872
3038
 
2873
3039
  };
2874
3040
 
3041
+ this._oncaptiondata = function(oData) {
3042
+
3043
+ /**
3044
+ * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
3045
+ *
3046
+ * @param {object} oData
3047
+ */
3048
+
3049
+ _s._wD('SMSound._oncaptiondata(): "' + this.id + '" caption data received.');
3050
+
3051
+ _t.captiondata = oData;
3052
+
3053
+ if (_t._iO.oncaptiondata) {
3054
+ _t._iO.oncaptiondata.apply(_t);
3055
+ }
3056
+
3057
+ };
3058
+
2875
3059
  this._onmetadata = function(oMDProps, oMDData) {
2876
3060
 
2877
3061
  /**
@@ -2882,7 +3066,7 @@ function SoundManager(smURL, smID) {
2882
3066
  * @param {array} oMDData (values)
2883
3067
  */
2884
3068
 
2885
- _s._wD('SMSound._onmetadata(): "' + this.sID + '" metadata received.');
3069
+ _s._wD('SMSound._onmetadata(): "' + this.id + '" metadata received.');
2886
3070
 
2887
3071
  var oData = {}, i, j;
2888
3072
 
@@ -2907,7 +3091,7 @@ function SoundManager(smURL, smID) {
2907
3091
  * @param {array} oID3Data (values)
2908
3092
  */
2909
3093
 
2910
- _s._wD('SMSound._onid3(): "' + this.sID + '" ID3 data received.');
3094
+ _s._wD('SMSound._onid3(): "' + this.id + '" ID3 data received.');
2911
3095
 
2912
3096
  var oData = [], i, j;
2913
3097
 
@@ -2927,14 +3111,14 @@ function SoundManager(smURL, smID) {
2927
3111
  this._onconnect = function(bSuccess) {
2928
3112
 
2929
3113
  bSuccess = (bSuccess === 1);
2930
- _s._wD('SMSound._onconnect(): "'+_t.sID+'"'+(bSuccess?' connected.':' failed to connect? - '+_t.url), (bSuccess?1:2));
3114
+ _s._wD('SMSound._onconnect(): "'+_t.id+'"'+(bSuccess?' connected.':' failed to connect? - '+_t.url), (bSuccess?1:2));
2931
3115
  _t.connected = bSuccess;
2932
3116
 
2933
3117
  if (bSuccess) {
2934
3118
 
2935
3119
  _t.failures = 0;
2936
3120
 
2937
- if (_idCheck(_t.sID)) {
3121
+ if (_idCheck(_t.id)) {
2938
3122
  if (_t.getAutoPlay()) {
2939
3123
  // only update the play state if auto playing
2940
3124
  _t.play(undefined, _t.getAutoPlay());
@@ -2986,25 +3170,183 @@ function SoundManager(smURL, smID) {
2986
3170
  _mixin = function(oMain, oAdd) {
2987
3171
 
2988
3172
  // non-destructive merge
2989
- var o1 = {}, i, o2, o;
3173
+ var o1 = (oMain || {}), o2, o;
2990
3174
 
2991
- // clone c1
2992
- for (i in oMain) {
2993
- if (oMain.hasOwnProperty(i)) {
2994
- o1[i] = oMain[i];
2995
- }
2996
- }
3175
+ // if unspecified, o2 is the default options object
3176
+ o2 = (typeof oAdd === 'undefined' ? _s.defaultOptions : oAdd);
2997
3177
 
2998
- o2 = (typeof oAdd === 'undefined'?_s.defaultOptions:oAdd);
2999
3178
  for (o in o2) {
3179
+
3000
3180
  if (o2.hasOwnProperty(o) && typeof o1[o] === 'undefined') {
3001
- o1[o] = o2[o];
3181
+
3182
+ if (typeof o2[o] !== 'object' || o2[o] === null) {
3183
+
3184
+ // assign directly
3185
+ o1[o] = o2[o];
3186
+
3187
+ } else {
3188
+
3189
+ // recurse through o2
3190
+ o1[o] = _mixin(o1[o], o2[o]);
3191
+
3192
+ }
3193
+
3002
3194
  }
3195
+
3003
3196
  }
3197
+
3004
3198
  return o1;
3005
3199
 
3006
3200
  };
3007
3201
 
3202
+ // additional soundManager properties that soundManager.setup() will accept
3203
+
3204
+ _extraOptions = {
3205
+ 'onready': 1,
3206
+ 'ontimeout': 1,
3207
+ 'defaultOptions': 1,
3208
+ 'flash9Options': 1,
3209
+ 'movieStarOptions': 1
3210
+ };
3211
+
3212
+ _assign = function(o, oParent) {
3213
+
3214
+ /**
3215
+ * recursive assignment of properties, soundManager.setup() helper
3216
+ * allows property assignment based on whitelist
3217
+ */
3218
+
3219
+ var i,
3220
+ result = true,
3221
+ hasParent = (typeof oParent !== 'undefined'),
3222
+ setupOptions = _s.setupOptions,
3223
+ extraOptions = _extraOptions;
3224
+
3225
+ // <d>
3226
+
3227
+ // if soundManager.setup() called, show accepted parameters.
3228
+
3229
+ if (typeof o === 'undefined') {
3230
+
3231
+ result = [];
3232
+
3233
+ for (i in setupOptions) {
3234
+
3235
+ if (setupOptions.hasOwnProperty(i)) {
3236
+ result.push(i);
3237
+ }
3238
+
3239
+ }
3240
+
3241
+ for (i in extraOptions) {
3242
+
3243
+ if (extraOptions.hasOwnProperty(i)) {
3244
+
3245
+ if (typeof _s[i] === 'object') {
3246
+
3247
+ result.push(i+': {...}');
3248
+
3249
+ } else if (_s[i] instanceof Function) {
3250
+
3251
+ result.push(i+': function() {...}');
3252
+
3253
+ } else {
3254
+
3255
+ result.push(i);
3256
+
3257
+ }
3258
+
3259
+ }
3260
+
3261
+ }
3262
+
3263
+ _s._wD(_str('setup', result.join(', ')));
3264
+
3265
+ return false;
3266
+
3267
+ }
3268
+
3269
+ // </d>
3270
+
3271
+ for (i in o) {
3272
+
3273
+ if (o.hasOwnProperty(i)) {
3274
+
3275
+ // if not an {object} we want to recurse through...
3276
+
3277
+ if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array) {
3278
+
3279
+ // check "allowed" options
3280
+
3281
+ if (hasParent && typeof extraOptions[oParent] !== 'undefined') {
3282
+
3283
+ // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
3284
+ _s[oParent][i] = o[i];
3285
+
3286
+ } else if (typeof setupOptions[i] !== 'undefined') {
3287
+
3288
+ // special case: assign to setupOptions object, which soundManager property references
3289
+ _s.setupOptions[i] = o[i];
3290
+
3291
+ // assign directly to soundManager, too
3292
+ _s[i] = o[i];
3293
+
3294
+ } else if (typeof extraOptions[i] === 'undefined') {
3295
+
3296
+ // invalid or disallowed parameter. complain.
3297
+ _complain(_str((typeof _s[i] === 'undefined' ? 'setupUndef' : 'setupError'), i), 2);
3298
+
3299
+ result = false;
3300
+
3301
+ } else {
3302
+
3303
+ /**
3304
+ * valid extraOptions parameter.
3305
+ * is it a method, like onready/ontimeout? call it.
3306
+ * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
3307
+ */
3308
+
3309
+ if (_s[i] instanceof Function) {
3310
+
3311
+ _s[i].apply(_s, (o[i] instanceof Array? o[i] : [o[i]]));
3312
+
3313
+ } else {
3314
+
3315
+ // good old-fashioned direct assignment
3316
+ _s[i] = o[i];
3317
+
3318
+ }
3319
+
3320
+ }
3321
+
3322
+ } else {
3323
+
3324
+ // recursion case, eg., { defaultOptions: { ... } }
3325
+
3326
+ if (typeof extraOptions[i] === 'undefined') {
3327
+
3328
+ // invalid or disallowed parameter. complain.
3329
+ _complain(_str((typeof _s[i] === 'undefined' ? 'setupUndef' : 'setupError'), i), 2);
3330
+
3331
+ result = false;
3332
+
3333
+ } else {
3334
+
3335
+ // recurse through object
3336
+ return _assign(o[i], i);
3337
+
3338
+ }
3339
+
3340
+ }
3341
+
3342
+ }
3343
+
3344
+ }
3345
+
3346
+ return result;
3347
+
3348
+ };
3349
+
3008
3350
  _event = (function() {
3009
3351
 
3010
3352
  var old = (_win.attachEvent),
@@ -3064,6 +3406,13 @@ function SoundManager(smURL, smID) {
3064
3406
 
3065
3407
  }());
3066
3408
 
3409
+ function _preferFlashCheck(kind) {
3410
+
3411
+ // whether flash should play a given type
3412
+ return (_s.preferFlash && _hasFlash && !_s.ignoreFlash && (typeof _s.flash[kind] !== 'undefined' && _s.flash[kind]));
3413
+
3414
+ }
3415
+
3067
3416
  /**
3068
3417
  * Internal HTML5 event handling
3069
3418
  * -----------------------------
@@ -3075,21 +3424,24 @@ function SoundManager(smURL, smID) {
3075
3424
 
3076
3425
  return function(e) {
3077
3426
 
3078
- var t = this._t;
3427
+ var t = this._t,
3428
+ result;
3079
3429
 
3080
3430
  if (!t || !t._a) {
3081
3431
  // <d>
3082
- if (t && t.sID) {
3083
- _s._wD(_h5+'ignoring '+e.type+': '+t.sID);
3432
+ if (t && t.id) {
3433
+ _s._wD(_h5+'ignoring '+e.type+': '+t.id);
3084
3434
  } else {
3085
3435
  _s._wD(_h5+'ignoring '+e.type);
3086
3436
  }
3087
3437
  // </d>
3088
- return null;
3438
+ result = null;
3089
3439
  } else {
3090
- return oFn.call(this, e);
3440
+ result = oFn.call(this, e);
3091
3441
  }
3092
3442
 
3443
+ return result;
3444
+
3093
3445
  };
3094
3446
 
3095
3447
  }
@@ -3100,7 +3452,7 @@ function SoundManager(smURL, smID) {
3100
3452
 
3101
3453
  abort: _html5_event(function() {
3102
3454
 
3103
- _s._wD(_h5+'abort: '+this._t.sID);
3455
+ _s._wD(_h5+'abort: '+this._t.id);
3104
3456
 
3105
3457
  }),
3106
3458
 
@@ -3117,9 +3469,11 @@ function SoundManager(smURL, smID) {
3117
3469
  }
3118
3470
 
3119
3471
  t._html5_canplay = true;
3120
- _s._wD(_h5+'canplay: '+t.sID+', '+t.url);
3472
+ _s._wD(_h5+'canplay: '+t.id+', '+t.url);
3121
3473
  t._onbufferchange(0);
3122
- position1K = (!isNaN(t.position)?t.position/1000:null);
3474
+
3475
+ // position according to instance options
3476
+ position1K = (typeof t._iO.position !== 'undefined' && !isNaN(t._iO.position)?t._iO.position/1000:null);
3123
3477
 
3124
3478
  // set the position if position was set before the sound loaded
3125
3479
  if (t.position && this.currentTime !== position1K) {
@@ -3127,7 +3481,7 @@ function SoundManager(smURL, smID) {
3127
3481
  try {
3128
3482
  this.currentTime = position1K;
3129
3483
  } catch(ee) {
3130
- _s._wD(_h5+'setting position failed: '+ee.message, 2);
3484
+ _s._wD(_h5+'setting position of ' + position1K + ' failed: '+ee.message, 2);
3131
3485
  }
3132
3486
  }
3133
3487
 
@@ -3138,14 +3492,13 @@ function SoundManager(smURL, smID) {
3138
3492
 
3139
3493
  }),
3140
3494
 
3141
- load: _html5_event(function() {
3495
+ canplaythrough: _html5_event(function() {
3142
3496
 
3143
3497
  var t = this._t;
3144
3498
 
3145
3499
  if (!t.loaded) {
3146
3500
  t._onbufferchange(0);
3147
- // should be 1, and the same
3148
- t._whileloading(t.bytesTotal, t.bytesTotal, t._get_html5_duration());
3501
+ t._whileloading(t.bytesLoaded, t.bytesTotal, t._get_html5_duration());
3149
3502
  t._onload(true);
3150
3503
  }
3151
3504
 
@@ -3155,7 +3508,7 @@ function SoundManager(smURL, smID) {
3155
3508
  /*
3156
3509
  emptied: _html5_event(function() {
3157
3510
 
3158
- _s._wD(_h5+'emptied: '+this._t.sID);
3511
+ _s._wD(_h5+'emptied: '+this._t.id);
3159
3512
 
3160
3513
  }),
3161
3514
  */
@@ -3164,7 +3517,7 @@ function SoundManager(smURL, smID) {
3164
3517
 
3165
3518
  var t = this._t;
3166
3519
 
3167
- _s._wD(_h5+'ended: '+t.sID);
3520
+ _s._wD(_h5+'ended: '+t.id);
3168
3521
  t._onfinish();
3169
3522
 
3170
3523
  }),
@@ -3179,31 +3532,26 @@ function SoundManager(smURL, smID) {
3179
3532
 
3180
3533
  loadeddata: _html5_event(function() {
3181
3534
 
3182
- var t = this._t,
3183
- // at least 1 byte, so math works
3184
- bytesTotal = t.bytesTotal || 1;
3535
+ var t = this._t;
3185
3536
 
3186
- _s._wD(_h5+'loadeddata: '+this._t.sID);
3537
+ _s._wD(_h5+'loadeddata: '+this._t.id);
3187
3538
 
3188
3539
  // safari seems to nicely report progress events, eventually totalling 100%
3189
3540
  if (!t._loaded && !_isSafari) {
3190
3541
  t.duration = t._get_html5_duration();
3191
- // fire whileloading() with 100% values
3192
- t._whileloading(bytesTotal, bytesTotal, t._get_html5_duration());
3193
- t._onload(true);
3194
3542
  }
3195
3543
 
3196
3544
  }),
3197
3545
 
3198
3546
  loadedmetadata: _html5_event(function() {
3199
3547
 
3200
- _s._wD(_h5+'loadedmetadata: '+this._t.sID);
3548
+ _s._wD(_h5+'loadedmetadata: '+this._t.id);
3201
3549
 
3202
3550
  }),
3203
3551
 
3204
3552
  loadstart: _html5_event(function() {
3205
3553
 
3206
- _s._wD(_h5+'loadstart: '+this._t.sID);
3554
+ _s._wD(_h5+'loadstart: '+this._t.id);
3207
3555
  // assume buffering at first
3208
3556
  this._t._onbufferchange(1);
3209
3557
 
@@ -3211,7 +3559,7 @@ function SoundManager(smURL, smID) {
3211
3559
 
3212
3560
  play: _html5_event(function() {
3213
3561
 
3214
- _s._wD(_h5+'play: '+this._t.sID+', '+this._t.url);
3562
+ _s._wD(_h5+'play: '+this._t.id+', '+this._t.url);
3215
3563
  // once play starts, no buffering
3216
3564
  this._t._onbufferchange(0);
3217
3565
 
@@ -3219,7 +3567,7 @@ function SoundManager(smURL, smID) {
3219
3567
 
3220
3568
  playing: _html5_event(function() {
3221
3569
 
3222
- _s._wD(_h5+'playing: '+this._t.sID);
3570
+ _s._wD(_h5+'playing: '+this._t.id);
3223
3571
 
3224
3572
  // once play starts, no buffering
3225
3573
  this._t._onbufferchange(0);
@@ -3228,6 +3576,8 @@ function SoundManager(smURL, smID) {
3228
3576
 
3229
3577
  progress: _html5_event(function(e) {
3230
3578
 
3579
+ // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
3580
+
3231
3581
  var t = this._t,
3232
3582
  i, j, str, buffered = 0,
3233
3583
  isProgress = (e.type === 'progress'),
@@ -3236,19 +3586,25 @@ function SoundManager(smURL, smID) {
3236
3586
  loaded = (e.loaded||0),
3237
3587
  total = (e.total||1);
3238
3588
 
3239
- if (t.loaded) {
3240
- return false;
3241
- }
3589
+ // reset the "buffered" (loaded byte ranges) array
3590
+ t.buffered = [];
3242
3591
 
3243
3592
  if (ranges && ranges.length) {
3244
3593
 
3245
3594
  // if loaded is 0, try TimeRanges implementation as % of load
3246
3595
  // https://developer.mozilla.org/en/DOM/TimeRanges
3247
3596
 
3248
- for (i=ranges.length-1; i >= 0; i--) {
3249
- buffered = (ranges.end(i) - ranges.start(i));
3597
+ // re-build "buffered" array
3598
+ for (i=0, j=ranges.length; i<j; i++) {
3599
+ t.buffered.push({
3600
+ 'start': ranges.start(i),
3601
+ 'end': ranges.end(i)
3602
+ });
3250
3603
  }
3251
3604
 
3605
+ // use the last value locally
3606
+ buffered = (ranges.end(0) - ranges.start(0));
3607
+
3252
3608
  // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
3253
3609
  loaded = buffered/e.target.duration;
3254
3610
 
@@ -3263,7 +3619,7 @@ function SoundManager(smURL, smID) {
3263
3619
  }
3264
3620
 
3265
3621
  if (isProgress && !isNaN(loaded)) {
3266
- _s._wD(_h5+'progress: '+t.sID+': ' + Math.floor(loaded*100)+'% loaded');
3622
+ _s._wD(_h5+'progress: '+t.id+': ' + Math.floor(loaded*100)+'% loaded');
3267
3623
  }
3268
3624
  // </d>
3269
3625
 
@@ -3273,10 +3629,11 @@ function SoundManager(smURL, smID) {
3273
3629
 
3274
3630
  // if progress, likely not buffering
3275
3631
  t._onbufferchange(0);
3632
+ // TODO: prevent calls with duplicate values.
3276
3633
  t._whileloading(loaded, total, t._get_html5_duration());
3277
3634
  if (loaded && total && loaded === total) {
3278
3635
  // in case "onload" doesn't fire (eg. gecko 1.9.2)
3279
- _html5_events.load.call(this, e);
3636
+ _html5_events.canplaythrough.call(this, e);
3280
3637
  }
3281
3638
 
3282
3639
  }
@@ -3285,7 +3642,7 @@ function SoundManager(smURL, smID) {
3285
3642
 
3286
3643
  ratechange: _html5_event(function() {
3287
3644
 
3288
- _s._wD(_h5+'ratechange: '+this._t.sID);
3645
+ _s._wD(_h5+'ratechange: '+this._t.id);
3289
3646
 
3290
3647
  }),
3291
3648
 
@@ -3294,7 +3651,7 @@ function SoundManager(smURL, smID) {
3294
3651
  // download paused/stopped, may have finished (eg. onload)
3295
3652
  var t = this._t;
3296
3653
 
3297
- _s._wD(_h5+'suspend: '+t.sID);
3654
+ _s._wD(_h5+'suspend: '+t.id);
3298
3655
  _html5_events.progress.call(this, e);
3299
3656
  t._onsuspend();
3300
3657
 
@@ -3302,7 +3659,7 @@ function SoundManager(smURL, smID) {
3302
3659
 
3303
3660
  stalled: _html5_event(function() {
3304
3661
 
3305
- _s._wD(_h5+'stalled: '+this._t.sID);
3662
+ _s._wD(_h5+'stalled: '+this._t.id);
3306
3663
 
3307
3664
  }),
3308
3665
 
@@ -3317,7 +3674,7 @@ function SoundManager(smURL, smID) {
3317
3674
  var t = this._t;
3318
3675
 
3319
3676
  // see also: seeking
3320
- _s._wD(_h5+'waiting: '+t.sID);
3677
+ _s._wD(_h5+'waiting: '+t.id);
3321
3678
 
3322
3679
  // playback faster than download rate, etc.
3323
3680
  t._onbufferchange(1);
@@ -3328,23 +3685,39 @@ function SoundManager(smURL, smID) {
3328
3685
 
3329
3686
  _html5OK = function(iO) {
3330
3687
 
3331
- // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
3332
- return (!iO.serverURL && (iO.type?_html5CanPlay({type:iO.type}):_html5CanPlay({url:iO.url})||_s.html5Only));
3688
+ // playability test based on URL or MIME type
3689
+
3690
+ var result;
3691
+
3692
+ if (iO.serverURL || (iO.type && _preferFlashCheck(iO.type))) {
3693
+
3694
+ // RTMP, or preferring flash
3695
+ result = false;
3696
+
3697
+ } else {
3698
+
3699
+ // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
3700
+ result = ((iO.type ? _html5CanPlay({type:iO.type}) : _html5CanPlay({url:iO.url}) || _s.html5Only));
3701
+
3702
+ }
3703
+
3704
+ return result;
3333
3705
 
3334
3706
  };
3335
3707
 
3336
- _html5Unload = function(oAudio) {
3708
+ _html5Unload = function(oAudio, url) {
3337
3709
 
3338
3710
  /**
3339
3711
  * Internal method: Unload media, and cancel any current/pending network requests.
3340
3712
  * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
3341
3713
  * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
3714
+ * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
3342
3715
  * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
3343
3716
  */
3344
3717
 
3345
3718
  if (oAudio) {
3346
- // Firefox likes '' for unload, most other UAs don't and fail to unload.
3347
- oAudio.src = (_is_firefox ? '' : _emptyURL);
3719
+ // Firefox likes '' for unload (used to work?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
3720
+ oAudio.src = url;
3348
3721
  }
3349
3722
 
3350
3723
  };
@@ -3371,17 +3744,10 @@ function SoundManager(smURL, smID) {
3371
3744
  fileExt,
3372
3745
  item;
3373
3746
 
3374
- function preferFlashCheck(kind) {
3375
-
3376
- // whether flash should play a given type
3377
- return (_s.preferFlash && _hasFlash && !_s.ignoreFlash && (typeof _s.flash[kind] !== 'undefined' && _s.flash[kind]));
3378
-
3379
- }
3380
-
3381
3747
  // account for known cases like audio/mp3
3382
3748
 
3383
3749
  if (mime && typeof _s.html5[mime] !== 'undefined') {
3384
- return (_s.html5[mime] && !preferFlashCheck(mime));
3750
+ return (_s.html5[mime] && !_preferFlashCheck(mime));
3385
3751
  }
3386
3752
 
3387
3753
  if (!_html5Ext) {
@@ -3402,7 +3768,7 @@ function SoundManager(smURL, smID) {
3402
3768
 
3403
3769
  if (!fileExt || !fileExt.length) {
3404
3770
  if (!mime) {
3405
- return false;
3771
+ result = false;
3406
3772
  } else {
3407
3773
  // audio/mp3 -> mp3, result should be known
3408
3774
  offset = mime.indexOf(';');
@@ -3416,15 +3782,17 @@ function SoundManager(smURL, smID) {
3416
3782
 
3417
3783
  if (fileExt && typeof _s.html5[fileExt] !== 'undefined') {
3418
3784
  // result known
3419
- return (_s.html5[fileExt] && !preferFlashCheck(fileExt));
3785
+ result = (_s.html5[fileExt] && !_preferFlashCheck(fileExt));
3420
3786
  } else {
3421
3787
  mime = 'audio/'+fileExt;
3422
3788
  result = _s.html5.canPlayType({type:mime});
3423
3789
  _s.html5[fileExt] = result;
3424
3790
  // _s._wD('canPlayType, found result: '+result);
3425
- return (result && _s.html5[mime] && !preferFlashCheck(mime));
3791
+ result = (result && _s.html5[mime] && !_preferFlashCheck(mime));
3426
3792
  }
3427
3793
 
3794
+ return result;
3795
+
3428
3796
  };
3429
3797
 
3430
3798
  _testHTML5 = function() {
@@ -3435,14 +3803,16 @@ function SoundManager(smURL, smID) {
3435
3803
 
3436
3804
  // 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. :/
3437
3805
  var a = (typeof Audio !== 'undefined' ? (_isOpera ? new Audio(null) : new Audio()) : null),
3438
- item, support = {}, aF, i;
3806
+ item, lookup, support = {}, aF, i;
3439
3807
 
3440
3808
  function _cp(m) {
3441
3809
 
3442
- var canPlay, i, j, isOK = false;
3810
+ var canPlay, i, j,
3811
+ result = false,
3812
+ isOK = false;
3443
3813
 
3444
3814
  if (!a || typeof a.canPlayType !== 'function') {
3445
- return false;
3815
+ return result;
3446
3816
  }
3447
3817
 
3448
3818
  if (m instanceof Array) {
@@ -3451,18 +3821,18 @@ function SoundManager(smURL, smID) {
3451
3821
  if (_s.html5[m[i]] || a.canPlayType(m[i]).match(_s.html5Test)) {
3452
3822
  isOK = true;
3453
3823
  _s.html5[m[i]] = true;
3454
-
3455
- // if flash can play and preferred, also mark it for use.
3456
- _s.flash[m[i]] = !!(_s.preferFlash && _hasFlash && m[i].match(_flashMIME));
3457
-
3824
+ // note flash support, too
3825
+ _s.flash[m[i]] = !!(m[i].match(_flashMIME));
3458
3826
  }
3459
3827
  }
3460
- return isOK;
3828
+ result = isOK;
3461
3829
  } else {
3462
3830
  canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);
3463
- return !!(canPlay && (canPlay.match(_s.html5Test)));
3831
+ result = !!(canPlay && (canPlay.match(_s.html5Test)));
3464
3832
  }
3465
3833
 
3834
+ return result;
3835
+
3466
3836
  }
3467
3837
 
3468
3838
  // test all registered formats + codecs
@@ -3470,29 +3840,46 @@ function SoundManager(smURL, smID) {
3470
3840
  aF = _s.audioFormats;
3471
3841
 
3472
3842
  for (item in aF) {
3843
+
3473
3844
  if (aF.hasOwnProperty(item)) {
3845
+
3846
+ lookup = 'audio/' + item;
3847
+
3474
3848
  support[item] = _cp(aF[item].type);
3475
3849
 
3476
3850
  // write back generic type too, eg. audio/mp3
3477
- support['audio/'+item] = support[item];
3851
+ support[lookup] = support[item];
3478
3852
 
3479
3853
  // assign flash
3480
- if (_s.preferFlash && !_s.ignoreFlash && item.match(_flashMIME)) {
3854
+ if (item.match(_flashMIME)) {
3855
+
3481
3856
  _s.flash[item] = true;
3857
+ _s.flash[lookup] = true;
3858
+
3482
3859
  } else {
3860
+
3483
3861
  _s.flash[item] = false;
3862
+ _s.flash[lookup] = false;
3863
+
3484
3864
  }
3485
3865
 
3486
3866
  // assign result to related formats, too
3867
+
3487
3868
  if (aF[item] && aF[item].related) {
3869
+
3488
3870
  for (i=aF[item].related.length-1; i >= 0; i--) {
3871
+
3489
3872
  // eg. audio/m4a
3490
3873
  support['audio/'+aF[item].related[i]] = support[item];
3491
3874
  _s.html5[aF[item].related[i]] = support[item];
3492
3875
  _s.flash[aF[item].related[i]] = support[item];
3876
+
3493
3877
  }
3878
+
3494
3879
  }
3880
+
3495
3881
  }
3882
+
3496
3883
  }
3497
3884
 
3498
3885
  support.canPlayType = (a?_cp:null);
@@ -3505,7 +3892,7 @@ function SoundManager(smURL, smID) {
3505
3892
  _strings = {
3506
3893
 
3507
3894
  // <d>
3508
- notReady: 'Not loaded yet - wait for soundManager.onload()/onready()',
3895
+ notReady: 'Not loaded yet - wait for soundManager.onready()',
3509
3896
  notOK: 'Audio support is not available.',
3510
3897
  domError: _smc + 'createMovie(): appendChild/innerHTML call failed. DOM not ready or other error.',
3511
3898
  spcWmode: _smc + 'createMovie(): Removing wmode, preventing known SWF loading issue(s)',
@@ -3513,28 +3900,28 @@ function SoundManager(smURL, smID) {
3513
3900
  tryDebug: 'Try ' + _sm + '.debugFlash = true for more security details (output goes to SWF.)',
3514
3901
  checkSWF: 'See SWF output for more debug info.',
3515
3902
  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/',
3516
- waitFocus: _sm + ': Special case: Waiting for focus-related event..',
3903
+ waitFocus: _sm + ': Special case: Waiting for SWF to load with window focus...',
3517
3904
  waitImpatient: _sm + ': Getting impatient, still waiting for Flash%s...',
3518
3905
  waitForever: _sm + ': Waiting indefinitely for Flash (will recover if unblocked)...',
3906
+ waitSWF: _sm + ': Retrying, waiting for 100% SWF load...',
3519
3907
  needFunction: _sm + ': Function object expected for %s',
3520
3908
  badID: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',
3521
3909
  currentObj: '--- ' + _sm + '._debug(): Current sound objects ---',
3522
- waitEI: _smc + 'initMovie(): Waiting for ExternalInterface call from Flash..',
3910
+ waitEI: _smc + 'initMovie(): Waiting for ExternalInterface call from Flash...',
3523
3911
  waitOnload: _sm + ': Waiting for window.onload()',
3524
3912
  docLoaded: _sm + ': Document already loaded',
3525
3913
  onload: _smc + 'initComplete(): calling soundManager.onload()',
3526
3914
  onloadOK: _sm + '.onload() complete',
3527
3915
  init: _smc + 'init()',
3528
3916
  didInit: _smc + 'init(): Already called?',
3529
- flashJS: _sm + ': Attempting to call Flash from JS..',
3917
+ flashJS: _sm + ': Attempting JS to Flash call...',
3530
3918
  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',
3531
3919
  badRemove: 'Warning: Failed to remove flash movie.',
3532
- noPeak: 'Warning: peakData features unsupported for movieStar formats',
3533
3920
  shutdown: _sm + '.disable(): Shutting down',
3534
3921
  queue: _sm + ': Queueing %s handler',
3535
3922
  smFail: _sm + ': Failed to initialise.',
3536
3923
  smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
3537
- fbTimeout: 'No flash response, applying .'+_swfCSS.swfTimedout+' CSS..',
3924
+ fbTimeout: 'No flash response, applying .'+_swfCSS.swfTimedout+' CSS...',
3538
3925
  fbLoaded: 'Flash loaded',
3539
3926
  fbHandler: _smc+'flashBlockHandler()',
3540
3927
  manURL: 'SMSound.load(): Using manually-assigned URL',
@@ -3545,7 +3932,12 @@ function SoundManager(smURL, smID) {
3545
3932
  needfl9: 'Note: Switching to flash 9, required for MP4 formats.',
3546
3933
  mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
3547
3934
  mfOn: 'mobileFlash::enabling on-screen flash repositioning',
3548
- policy: 'Enabling usePolicyFile for data access'
3935
+ policy: 'Enabling usePolicyFile for data access',
3936
+ setup: _sm + '.setup(): allowed parameters: %s',
3937
+ setupError: _sm + '.setup(): "%s" cannot be assigned with this method.',
3938
+ setupUndef: _sm + '.setup(): Could not find option "%s"',
3939
+ setupLate: _sm + '.setup(): url + flashVersion changes will not take effect until reboot().',
3940
+ h5a: 'creating HTML5 Audio() object'
3549
3941
  // </d>
3550
3942
 
3551
3943
  };
@@ -3804,11 +4196,7 @@ function SoundManager(smURL, smID) {
3804
4196
  // <d>
3805
4197
  _wDS = function(o, errorLevel) {
3806
4198
 
3807
- if (!o) {
3808
- return '';
3809
- } else {
3810
- return _s._wD(_str(o), errorLevel);
3811
- }
4199
+ return (!o ? '' : _s._wD(_str(o), errorLevel));
3812
4200
 
3813
4201
  };
3814
4202
 
@@ -3938,11 +4326,11 @@ function SoundManager(smURL, smID) {
3938
4326
 
3939
4327
  _processOnEvents = function(oOptions) {
3940
4328
 
3941
- // assume onready, if unspecified
4329
+ // if unspecified, assume OK/error
3942
4330
 
3943
4331
  if (!oOptions) {
3944
4332
  oOptions = {
3945
- type: 'onready'
4333
+ type: (_s.ok() ? 'onready' : 'ontimeout')
3946
4334
  };
3947
4335
  }
3948
4336
 
@@ -3951,7 +4339,7 @@ function SoundManager(smURL, smID) {
3951
4339
  return false;
3952
4340
  }
3953
4341
 
3954
- if (oOptions.type === 'ontimeout' && _s.ok()) {
4342
+ if (oOptions.type === 'ontimeout' && (_s.ok() || (_disabled && !oOptions.ignoreInit))) {
3955
4343
  // invalid case
3956
4344
  return false;
3957
4345
  }
@@ -4008,7 +4396,7 @@ function SoundManager(smURL, smID) {
4008
4396
 
4009
4397
  // call user-defined "onload", scoped to window
4010
4398
 
4011
- if (_s.onload instanceof Function) {
4399
+ if (typeof _s.onload === 'function') {
4012
4400
  _wDS('onload', 1);
4013
4401
  _s.onload.apply(_win);
4014
4402
  _wDS('onloadOK', 1);
@@ -4026,7 +4414,7 @@ function SoundManager(smURL, smID) {
4026
4414
 
4027
4415
  // 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
4028
4416
 
4029
- if (_hasFlash !== undefined) {
4417
+ if (typeof _hasFlash !== 'undefined') {
4030
4418
  // this work has already been done.
4031
4419
  return _hasFlash;
4032
4420
  }
@@ -4056,9 +4444,11 @@ function SoundManager(smURL, smID) {
4056
4444
 
4057
4445
  _featureCheck = function() {
4058
4446
 
4059
- var needsFlash, item,
4060
-
4061
- // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (iPad) + iOS4 works.
4447
+ var needsFlash,
4448
+ item,
4449
+ result = true,
4450
+ formats = _s.audioFormats,
4451
+ // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
4062
4452
  isSpecial = (_is_iDevice && !!(_ua.match(/os (1|2|3_0|3_1)/i)));
4063
4453
 
4064
4454
  if (isSpecial) {
@@ -4073,42 +4463,43 @@ function SoundManager(smURL, smID) {
4073
4463
  _s.oMC.style.display = 'none';
4074
4464
  }
4075
4465
 
4076
- return false;
4466
+ result = false;
4077
4467
 
4078
- }
4468
+ } else {
4079
4469
 
4080
- if (_s.useHTML5Audio) {
4470
+ if (_s.useHTML5Audio) {
4081
4471
 
4082
- if (!_s.html5 || !_s.html5.canPlayType) {
4083
- _s._wD('SoundManager: No HTML5 Audio() support detected.');
4084
- _s.hasHTML5 = false;
4085
- return true;
4086
- } else {
4087
- _s.hasHTML5 = true;
4088
- }
4089
- if (_isBadSafari) {
4090
- _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);
4091
- if (_detectFlash()) {
4092
- return true;
4472
+ if (!_s.html5 || !_s.html5.canPlayType) {
4473
+ _s._wD('SoundManager: No HTML5 Audio() support detected.');
4474
+ _s.hasHTML5 = false;
4475
+ } else {
4476
+ _s.hasHTML5 = true;
4093
4477
  }
4094
- }
4095
- } else {
4096
4478
 
4097
- // flash needed (or, HTML5 needs enabling.)
4098
- return true;
4479
+ // <d>
4480
+ if (_isBadSafari) {
4481
+ _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);
4482
+ }
4483
+ // </d>
4484
+
4485
+ }
4099
4486
 
4100
4487
  }
4101
4488
 
4102
- for (item in _s.audioFormats) {
4103
- if (_s.audioFormats.hasOwnProperty(item)) {
4104
- if ((_s.audioFormats[item].required && !_s.html5.canPlayType(_s.audioFormats[item].type)) || _s.flash[item] || _s.flash[_s.audioFormats[item].type]) {
4105
- // flash may be required, or preferred for this format
4106
- needsFlash = true;
4489
+ if (_s.useHTML5Audio && _s.hasHTML5) {
4490
+
4491
+ for (item in formats) {
4492
+ if (formats.hasOwnProperty(item)) {
4493
+ if ((formats[item].required && !_s.html5.canPlayType(formats[item].type)) || (_s.preferFlash && (_s.flash[item] || _s.flash[formats[item].type]))) {
4494
+ // flash may be required, or preferred for this format
4495
+ needsFlash = true;
4496
+ }
4107
4497
  }
4108
4498
  }
4499
+
4109
4500
  }
4110
4501
 
4111
- // sanity check..
4502
+ // sanity check...
4112
4503
  if (_s.ignoreFlash) {
4113
4504
  needsFlash = false;
4114
4505
  }
@@ -4126,7 +4517,7 @@ function SoundManager(smURL, smID) {
4126
4517
  * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
4127
4518
  */
4128
4519
 
4129
- var i, j, result = 0;
4520
+ var i, j, urlResult = 0, result;
4130
4521
 
4131
4522
  if (url instanceof Array) {
4132
4523
 
@@ -4136,32 +4527,34 @@ function SoundManager(smURL, smID) {
4136
4527
  if (url[i] instanceof Object) {
4137
4528
  // MIME check
4138
4529
  if (_s.canPlayMIME(url[i].type)) {
4139
- result = i;
4530
+ urlResult = i;
4140
4531
  break;
4141
4532
  }
4142
4533
 
4143
4534
  } else if (_s.canPlayURL(url[i])) {
4144
4535
  // URL string check
4145
- result = i;
4536
+ urlResult = i;
4146
4537
  break;
4147
4538
  }
4148
4539
 
4149
4540
  }
4150
4541
 
4151
4542
  // normalize to string
4152
- if (url[result].url) {
4153
- url[result] = url[result].url;
4543
+ if (url[urlResult].url) {
4544
+ url[urlResult] = url[urlResult].url;
4154
4545
  }
4155
4546
 
4156
- return url[result];
4547
+ result = url[urlResult];
4157
4548
 
4158
4549
  } else {
4159
4550
 
4160
4551
  // single URL case
4161
- return url;
4552
+ result = url;
4162
4553
 
4163
4554
  }
4164
4555
 
4556
+ return result;
4557
+
4165
4558
  };
4166
4559
 
4167
4560
 
@@ -4175,11 +4568,11 @@ function SoundManager(smURL, smID) {
4175
4568
 
4176
4569
  oSound._hasTimer = true;
4177
4570
 
4178
- if (!_likesHTML5 && _s.html5PollingInterval) {
4571
+ if (!_mobileHTML5 && _s.html5PollingInterval) {
4179
4572
 
4180
4573
  if (_h5IntervalTimer === null && _h5TimerCount === 0) {
4181
4574
 
4182
- _h5IntervalTimer = window.setInterval(_timerExecute, _s.html5PollingInterval);
4575
+ _h5IntervalTimer = _win.setInterval(_timerExecute, _s.html5PollingInterval);
4183
4576
 
4184
4577
  }
4185
4578
 
@@ -4201,7 +4594,7 @@ function SoundManager(smURL, smID) {
4201
4594
 
4202
4595
  oSound._hasTimer = false;
4203
4596
 
4204
- if (!_likesHTML5 && _s.html5PollingInterval) {
4597
+ if (!_mobileHTML5 && _s.html5PollingInterval) {
4205
4598
 
4206
4599
  // interval will stop itself at next execution.
4207
4600
 
@@ -4225,7 +4618,7 @@ function SoundManager(smURL, smID) {
4225
4618
 
4226
4619
  // no active timers, stop polling interval.
4227
4620
 
4228
- window.clearInterval(_h5IntervalTimer);
4621
+ _win.clearInterval(_h5IntervalTimer);
4229
4622
 
4230
4623
  _h5IntervalTimer = null;
4231
4624
 
@@ -4251,7 +4644,7 @@ function SoundManager(smURL, smID) {
4251
4644
 
4252
4645
  options = (typeof options !== 'undefined' ? options : {});
4253
4646
 
4254
- if (_s.onerror instanceof Function) {
4647
+ if (typeof _s.onerror === 'function') {
4255
4648
  _s.onerror.apply(_win, [{type:(typeof options.type !== 'undefined' ? options.type : null)}]);
4256
4649
  }
4257
4650
 
@@ -4363,12 +4756,8 @@ function SoundManager(smURL, smID) {
4363
4756
  }
4364
4757
  // </d>
4365
4758
 
4366
- if (_isIE) {
4367
- // IE needs a timeout OR delay until window.onload - may need TODO: investigating
4368
- setTimeout(_init, 100);
4369
- } else {
4370
- _init();
4371
- }
4759
+ // slight delay before init
4760
+ setTimeout(_init, _isIE ? 100 : 1);
4372
4761
 
4373
4762
  };
4374
4763
 
@@ -4411,7 +4800,7 @@ function SoundManager(smURL, smID) {
4411
4800
  localURL = (_s.altURL || remoteURL),
4412
4801
  swfTitle = 'JS/Flash audio component (SoundManager 2)',
4413
4802
  oEmbed, oMovie, oTarget = _getDocument(), tmp, movieHTML, oEl, extraClass = _getSWFCSS(),
4414
- s, x, sClass, side = null, isRTL = null,
4803
+ s, x, sClass, isRTL = null,
4415
4804
  html = _doc.getElementsByTagName('html')[0];
4416
4805
 
4417
4806
  isRTL = (html && html.dir && html.dir.match(/rtl/i));
@@ -4453,12 +4842,6 @@ function SoundManager(smURL, smID) {
4453
4842
  'hasPriority': 'true'
4454
4843
  };
4455
4844
 
4456
- if (side !== null) {
4457
- // don't specify width/height if null.
4458
- oEmbed.width = side;
4459
- oEmbed.height = side;
4460
- }
4461
-
4462
4845
  if (_s.debugFlash) {
4463
4846
  oEmbed.FlashVars = 'debug=1';
4464
4847
  }
@@ -4473,7 +4856,7 @@ function SoundManager(smURL, smID) {
4473
4856
  // IE is "special".
4474
4857
  oMovie = _doc.createElement('div');
4475
4858
  movieHTML = [
4476
- '<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 + '">',
4859
+ '<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">',
4477
4860
  param('movie', smURL),
4478
4861
  param('AllowScriptAccess', _s.allowScriptAccess),
4479
4862
  param('quality', oEmbed.quality),
@@ -4629,7 +5012,7 @@ function SoundManager(smURL, smID) {
4629
5012
  }
4630
5013
  // </d>
4631
5014
 
4632
- if (_s.oninitmovie instanceof Function) {
5015
+ if (typeof _s.oninitmovie === 'function') {
4633
5016
  setTimeout(_s.oninitmovie, 1);
4634
5017
  }
4635
5018
 
@@ -4645,6 +5028,9 @@ function SoundManager(smURL, smID) {
4645
5028
 
4646
5029
  _waitForEI = function() {
4647
5030
 
5031
+ var p,
5032
+ loadIncomplete = false;
5033
+
4648
5034
  if (_waitingForEI) {
4649
5035
  return false;
4650
5036
  }
@@ -4653,20 +5039,30 @@ function SoundManager(smURL, smID) {
4653
5039
  _event.remove(_win, 'load', _delayWaitForEI);
4654
5040
 
4655
5041
  if (_tryInitOnFocus && !_isFocused) {
4656
- // giant Safari 3.1 hack - assume mousemove = focus given lack of focus event
5042
+ // Safari won't load flash in background tabs, only when focused.
4657
5043
  _wDS('waitFocus');
4658
5044
  return false;
4659
5045
  }
4660
5046
 
4661
- var p;
4662
5047
  if (!_didInit) {
4663
5048
  p = _s.getMoviePercent();
4664
- _s._wD(_str('waitImpatient', (p === 100?' (SWF loaded)':(p > 0?' (SWF ' + p + '% loaded)':''))));
5049
+ _s._wD(_str('waitImpatient', (p > 0 ? ' (SWF ' + p + '% loaded)' : '')));
5050
+ if (p > 0 && p < 100) {
5051
+ loadIncomplete = true;
5052
+ }
4665
5053
  }
4666
5054
 
4667
5055
  setTimeout(function() {
4668
5056
 
4669
- p = _s.getMoviePercent();
5057
+ p = _s.getMoviePercent();
5058
+
5059
+ if (loadIncomplete) {
5060
+ // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
5061
+ _waitingForEI = false;
5062
+ _s._wD(_str('waitSWF'));
5063
+ _win.setTimeout(_delayWaitForEI, 1);
5064
+ return false;
5065
+ }
4670
5066
 
4671
5067
  // <d>
4672
5068
  if (!_didInit) {
@@ -4717,25 +5113,24 @@ function SoundManager(smURL, smID) {
4717
5113
 
4718
5114
  function cleanup() {
4719
5115
  _event.remove(_win, 'focus', _handleFocus);
4720
- _event.remove(_win, 'load', _handleFocus);
4721
5116
  }
4722
5117
 
4723
5118
  if (_isFocused || !_tryInitOnFocus) {
5119
+ // already focused, or not special Safari background tab case
4724
5120
  cleanup();
4725
5121
  return true;
4726
5122
  }
4727
5123
 
4728
5124
  _okToDisable = true;
4729
5125
  _isFocused = true;
4730
- _s._wD(_smc+'handleFocus()');
4731
-
4732
- if (_isSafari && _tryInitOnFocus) {
4733
- _event.remove(_win, 'mousemove', _handleFocus);
4734
- }
5126
+ _s._wD(_sm+': Got window focus.');
4735
5127
 
4736
5128
  // allow init to restart
4737
5129
  _waitingForEI = false;
4738
5130
 
5131
+ // kick off ExternalInterface timeout, now that the SWF has started
5132
+ _delayWaitForEI();
5133
+
4739
5134
  cleanup();
4740
5135
  return true;
4741
5136
 
@@ -4772,6 +5167,7 @@ function SoundManager(smURL, smID) {
4772
5167
  }
4773
5168
 
4774
5169
  var wasTimeout = (_s.useFlashBlock && _s.flashLoadTimeout && !_s.getMoviePercent()),
5170
+ result = true,
4775
5171
  error;
4776
5172
 
4777
5173
  if (!wasTimeout) {
@@ -4787,31 +5183,66 @@ function SoundManager(smURL, smID) {
4787
5183
  if (_s.useFlashBlock && _s.oMC) {
4788
5184
  _s.oMC.className = _getSWFCSS() + ' ' + (_s.getMoviePercent() === null?_swfCSS.swfTimedout:_swfCSS.swfError);
4789
5185
  }
4790
- _processOnEvents({type:'ontimeout', error:error});
5186
+ _processOnEvents({type:'ontimeout', error:error, ignoreInit: true});
4791
5187
  _debugTS('onload', false);
4792
5188
  _catchError(error);
4793
- return false;
5189
+ result = false;
4794
5190
  } else {
4795
5191
  _debugTS('onload', true);
4796
5192
  }
4797
5193
 
4798
- if (_s.waitForWindowLoad && !_windowLoaded) {
4799
- _wDS('waitOnload');
4800
- _event.add(_win, 'load', _initUserOnload);
4801
- return false;
4802
- } else {
4803
- // <d>
4804
- if (_s.waitForWindowLoad && _windowLoaded) {
4805
- _wDS('docLoaded');
5194
+ if (!_disabled) {
5195
+ if (_s.waitForWindowLoad && !_windowLoaded) {
5196
+ _wDS('waitOnload');
5197
+ _event.add(_win, 'load', _initUserOnload);
5198
+ } else {
5199
+ // <d>
5200
+ if (_s.waitForWindowLoad && _windowLoaded) {
5201
+ _wDS('docLoaded');
5202
+ }
5203
+ // </d>
5204
+ _initUserOnload();
4806
5205
  }
4807
- // </d>
4808
- _initUserOnload();
4809
5206
  }
4810
5207
 
4811
- return true;
5208
+ return result;
4812
5209
 
4813
5210
  };
4814
5211
 
5212
+ /**
5213
+ * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
5214
+ * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
5215
+ */
5216
+
5217
+ _setProperties = function() {
5218
+
5219
+ var i,
5220
+ o = _s.setupOptions;
5221
+
5222
+ for (i in o) {
5223
+
5224
+ if (o.hasOwnProperty(i)) {
5225
+
5226
+ // assign local property if not already defined
5227
+
5228
+ if (typeof _s[i] === 'undefined') {
5229
+
5230
+ _s[i] = o[i];
5231
+
5232
+ } else if (_s[i] !== o[i]) {
5233
+
5234
+ // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
5235
+ _s.setupOptions[i] = _s[i];
5236
+
5237
+ }
5238
+
5239
+ }
5240
+
5241
+ }
5242
+
5243
+ };
5244
+
5245
+
4815
5246
  _init = function() {
4816
5247
 
4817
5248
  _wDS('init');
@@ -4894,6 +5325,10 @@ function SoundManager(smURL, smID) {
4894
5325
  }
4895
5326
 
4896
5327
  _didDCLoaded = true;
5328
+
5329
+ // assign top-level soundManager properties eg. soundManager.url
5330
+ _setProperties();
5331
+
4897
5332
  _initDebug();
4898
5333
 
4899
5334
  /**
@@ -4903,15 +5338,21 @@ function SoundManager(smURL, smID) {
4903
5338
  // <d>
4904
5339
  (function(){
4905
5340
 
4906
- var a = 'sm2-usehtml5audio=', l = _wl.toLowerCase(), b = null,
4907
- a2 = 'sm2-preferflash=', b2 = null, hasCon = (typeof console !== 'undefined' && typeof console.log !== 'undefined');
5341
+ var a = 'sm2-usehtml5audio=',
5342
+ a2 = 'sm2-preferflash=',
5343
+ b = null,
5344
+ b2 = null,
5345
+ hasCon = (typeof console !== 'undefined' && typeof console.log === 'function'),
5346
+ l = _wl.toLowerCase();
4908
5347
 
4909
5348
  if (l.indexOf(a) !== -1) {
4910
5349
  b = (l.charAt(l.indexOf(a)+a.length) === '1');
4911
5350
  if (hasCon) {
4912
5351
  console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
4913
5352
  }
4914
- _s.useHTML5Audio = b;
5353
+ _s.setup({
5354
+ 'useHTML5Audio': b
5355
+ });
4915
5356
  }
4916
5357
 
4917
5358
  if (l.indexOf(a2) !== -1) {
@@ -4919,7 +5360,9 @@ function SoundManager(smURL, smID) {
4919
5360
  if (hasCon) {
4920
5361
  console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
4921
5362
  }
4922
- _s.preferFlash = b2;
5363
+ _s.setup({
5364
+ 'preferFlash': b2
5365
+ });
4923
5366
  }
4924
5367
 
4925
5368
  }());
@@ -4927,10 +5370,12 @@ function SoundManager(smURL, smID) {
4927
5370
 
4928
5371
  if (!_hasFlash && _s.hasHTML5) {
4929
5372
  _s._wD('SoundManager: No Flash detected'+(!_s.useHTML5Audio?', enabling HTML5.':'. Trying HTML5-only mode.'));
4930
- _s.useHTML5Audio = true;
4931
- // make sure we aren't preferring flash, either
4932
- // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
4933
- _s.preferFlash = false;
5373
+ _s.setup({
5374
+ 'useHTML5Audio': true,
5375
+ // make sure we aren't preferring flash, either
5376
+ // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
5377
+ 'preferFlash': false
5378
+ });
4934
5379
  }
4935
5380
 
4936
5381
  _testHTML5();
@@ -4942,7 +5387,9 @@ function SoundManager(smURL, smID) {
4942
5387
  _s._wD('SoundManager: Fatal error: Flash is needed to play some required formats, but is not available.');
4943
5388
  // TODO: Fatal here vs. timeout approach, etc.
4944
5389
  // hack: fail sooner.
4945
- _s.flashLoadTimeout = 1;
5390
+ _s.setup({
5391
+ 'flashLoadTimeout': 1
5392
+ });
4946
5393
  }
4947
5394
 
4948
5395
  if (_doc.removeEventListener) {
@@ -4976,16 +5423,9 @@ function SoundManager(smURL, smID) {
4976
5423
 
4977
5424
  // focus and window load, init (primarily flash-driven)
4978
5425
  _event.add(_win, 'focus', _handleFocus);
4979
- _event.add(_win, 'load', _handleFocus);
4980
5426
  _event.add(_win, 'load', _delayWaitForEI);
4981
5427
  _event.add(_win, 'load', _winOnLoad);
4982
5428
 
4983
-
4984
- if (_isSafari && _tryInitOnFocus) {
4985
- // massive Safari 3.1 focus detection hack
4986
- _event.add(_win, 'mousemove', _handleFocus);
4987
- }
4988
-
4989
5429
  if (_doc.addEventListener) {
4990
5430
 
4991
5431
  _doc.addEventListener('DOMContentLoaded', _domContentLoaded, false);
@@ -5023,4 +5463,4 @@ if (typeof SM2_DEFER === 'undefined' || !SM2_DEFER) {
5023
5463
  window.SoundManager = SoundManager; // constructor
5024
5464
  window.soundManager = soundManager; // public API, flash callbacks etc.
5025
5465
 
5026
- }(window));
5466
+ }(window));