soundmanager-rails 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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));