soundmanager-rails 0.1.4 → 0.1.5

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,11 +8,11 @@
8
8
  * Code provided under the BSD License:
9
9
  * http://schillmania.com/projects/soundmanager2/license.txt
10
10
  *
11
- * V2.97a.20130101
11
+ * V2.97a.20130512
12
12
  */
13
13
 
14
14
  /*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio, opera */
15
- /*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true */
15
+ /*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true, todo: true */
16
16
 
17
17
  /**
18
18
  * About this file
@@ -74,7 +74,8 @@ function SoundManager(smURL, smID) {
74
74
  'useHTML5Audio': true, // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
75
75
  'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
76
76
  '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.
77
- 'noSWFCache': false // if true, appends ?ts={date} to break aggressive SWF caching.
77
+ 'noSWFCache': false, // if true, appends ?ts={date} to break aggressive SWF caching.
78
+ 'idPrefix': 'sound' // if an id is not provided to createSound(), this prefix is used for generated IDs - 'sound0', 'sound1' etc.
78
79
 
79
80
  };
80
81
 
@@ -166,6 +167,11 @@ function SoundManager(smURL, smID) {
166
167
  'required': false
167
168
  },
168
169
 
170
+ 'opus': {
171
+ 'type': ['audio/ogg; codecs=opus', 'audio/opus'],
172
+ 'required': false
173
+ },
174
+
169
175
  'wav': {
170
176
  'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
171
177
  'required': false
@@ -183,7 +189,7 @@ function SoundManager(smURL, smID) {
183
189
 
184
190
  // dynamic attributes
185
191
 
186
- this.versionNumber = 'V2.97a.20130101';
192
+ this.versionNumber = 'V2.97a.20130512';
187
193
  this.version = null;
188
194
  this.movieURL = null;
189
195
  this.altURL = null;
@@ -260,11 +266,11 @@ function SoundManager(smURL, smID) {
260
266
 
261
267
  var SMSound,
262
268
  sm2 = this, globalHTML5Audio = null, flash = null, sm = 'soundManager', smc = sm + ': ', h5 = 'HTML5::', id, ua = navigator.userAgent, wl = window.location.href.toString(), doc = document, doNothing, setProperties, init, fV, on_queue = [], debugOpen = true, debugTS, didAppend = false, appendSuccess = false, didInit = false, disabled = false, windowLoaded = false, _wDS, wdCount = 0, initComplete, mixin, assign, extraOptions, addOnEvent, processOnEvents, initUserOnload, delayWaitForEI, waitForEI, setVersionInfo, handleFocus, strings, initMovie, preInit, domContentLoaded, winOnLoad, didDCLoaded, getDocument, createMovie, catchError, setPolling, initDebug, debugLevels = ['log', 'info', 'warn', 'error'], defaultFlashVersion = 8, disableObject, failSafely, normalizeMovieURL, oRemoved = null, oRemovedHTML = null, str, flashBlockHandler, getSWFCSS, swfCSS, toggleDebug, loopFix, policyFix, complain, idCheck, waitingForEI = false, initPending = false, startTimer, stopTimer, timerExecute, h5TimerCount = 0, h5IntervalTimer = null, parseURL, messages = [],
263
- needsFlash = null, featureCheck, html5OK, html5CanPlay, html5Ext, html5Unload, domContentLoadedIE, testHTML5, event, slice = Array.prototype.slice, useGlobalHTML5Audio = false, lastGlobalHTML5URL, hasFlash, detectFlash, badSafariFix, html5_events, showSupport, flushMessages,
264
- is_iDevice = ua.match(/(ipad|iphone|ipod)/i), isAndroid = ua.match(/android/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)),
269
+ canIgnoreFlash, needsFlash = null, featureCheck, html5OK, html5CanPlay, html5Ext, html5Unload, domContentLoadedIE, testHTML5, event, slice = Array.prototype.slice, useGlobalHTML5Audio = false, lastGlobalHTML5URL, hasFlash, detectFlash, badSafariFix, html5_events, showSupport, flushMessages, wrapCallback, idCounter = 0,
270
+ is_iDevice = ua.match(/(ipad|iphone|ipod)/i), isAndroid = ua.match(/android/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)), isFirefox = (ua.match(/firefox/i)),
265
271
  mobileHTML5 = (ua.match(/(mobile|pre\/|xoom)/i) || is_iDevice || isAndroid),
266
272
  isBadSafari = (!wl.match(/usehtml5audio/i) && !wl.match(/sm2\-ignorebadua/i) && isSafari && !ua.match(/silk/i) && ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159
267
- hasConsole = (window.console !== _undefined && console.log !== _undefined), isFocused = (doc.hasFocus !== _undefined?doc.hasFocus():null), tryInitOnFocus = (isSafari && (doc.hasFocus === _undefined || !doc.hasFocus())), okToDisable = !tryInitOnFocus, flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i,
273
+ hasConsole = (window.console !== _undefined && console.log !== _undefined), isFocused = (doc.hasFocus !== _undefined?doc.hasFocus():null), tryInitOnFocus = (isSafari && (doc.hasFocus === _undefined || !doc.hasFocus())), okToDisable = !tryInitOnFocus, flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i, msecScale = 1000,
268
274
  emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
269
275
  overHTTP = (doc.location?doc.location.protocol.match(/http/i):null),
270
276
  http = (!overHTTP ? 'http:/'+'/' : ''),
@@ -336,14 +342,18 @@ function SoundManager(smURL, smID) {
336
342
 
337
343
  // special case 1: "Late setup". SM2 loaded normally, but user didn't assign flash URL eg., setup({url:...}) before SM2 init. Treat as delayed init.
338
344
 
339
- if (noURL && didDCLoaded && options.url !== _undefined) {
340
- sm2.beginDelayedInit();
341
- }
345
+ if (options) {
342
346
 
343
- // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
347
+ if (noURL && didDCLoaded && options.url !== _undefined) {
348
+ sm2.beginDelayedInit();
349
+ }
350
+
351
+ // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
352
+
353
+ if (!didDCLoaded && options.url !== _undefined && doc.readyState === 'complete') {
354
+ setTimeout(domContentLoaded, 1);
355
+ }
344
356
 
345
- if (!didDCLoaded && options.url !== _undefined && doc.readyState === 'complete') {
346
- setTimeout(domContentLoaded, 1);
347
357
  }
348
358
 
349
359
  return sm2;
@@ -352,7 +362,7 @@ function SoundManager(smURL, smID) {
352
362
 
353
363
  this.ok = function() {
354
364
 
355
- return (needsFlash?(didInit && !disabled):(sm2.useHTML5Audio && sm2.hasHTML5));
365
+ return (needsFlash ? (didInit && !disabled) : (sm2.useHTML5Audio && sm2.hasHTML5));
356
366
 
357
367
  };
358
368
 
@@ -387,7 +397,7 @@ function SoundManager(smURL, smID) {
387
397
  }
388
398
 
389
399
  if (_url !== _undefined) {
390
- // function overloading in JS! :) ..assume simple createSound(id,url) use case
400
+ // function overloading in JS! :) ..assume simple createSound(id, url) use case
391
401
  oOptions = {
392
402
  'id': oOptions,
393
403
  'url': _url
@@ -399,12 +409,17 @@ function SoundManager(smURL, smID) {
399
409
 
400
410
  options.url = parseURL(options.url);
401
411
 
412
+ // generate an id, if needed.
413
+ if (options.id === undefined) {
414
+ options.id = sm2.setupOptions.idPrefix + (idCounter++);
415
+ }
416
+
402
417
  // <d>
403
418
  if (options.id.toString().charAt(0).match(/^[0-9]$/)) {
404
419
  sm2._wD(cs + str('badID', options.id), 2);
405
420
  }
406
421
 
407
- sm2._wD(cs + options.id + ' (' + options.url + ')', 1);
422
+ sm2._wD(cs + options.id + (options.url ? ' (' + options.url + ')' : ''), 1);
408
423
  // </d>
409
424
 
410
425
  if (idCheck(options.id, true)) {
@@ -429,10 +444,23 @@ function SoundManager(smURL, smID) {
429
444
 
430
445
  } else {
431
446
 
447
+ if (sm2.html5Only) {
448
+ sm2._wD(options.id + ': No HTML5 support for this sound, and no Flash. Exiting.');
449
+ return make();
450
+ }
451
+
452
+ // TODO: Move HTML5/flash checks into generic URL parsing/handling function.
453
+
454
+ if (sm2.html5.usingFlash && options.url && options.url.match(/data\:/i)) {
455
+ // data: URIs not supported by Flash, either.
456
+ sm2._wD(options.id + ': data: URIs not supported via Flash. Exiting.');
457
+ return make();
458
+ }
459
+
432
460
  if (fV > 8) {
433
461
  if (options.isMovieStar === null) {
434
462
  // attempt to detect MPEG-4 formats
435
- options.isMovieStar = !!(options.serverURL || (options.type ? options.type.match(netStreamMimeTypes) : false) || options.url.match(netStreamPattern));
463
+ options.isMovieStar = !!(options.serverURL || (options.type ? options.type.match(netStreamMimeTypes) : false) || (options.url && options.url.match(netStreamPattern)));
436
464
  }
437
465
  // <d>
438
466
  if (options.isMovieStar) {
@@ -598,30 +626,50 @@ function SoundManager(smURL, smID) {
598
626
 
599
627
  this.play = function(sID, oOptions) {
600
628
 
601
- var result = false;
629
+ var result = null,
630
+ // legacy function-overloading use case: play('mySound', '/path/to/some.mp3');
631
+ overloaded = (oOptions && !(oOptions instanceof Object));
602
632
 
603
633
  if (!didInit || !sm2.ok()) {
604
634
  complain(sm + '.play(): ' + str(!didInit?'notReady':'notOK'));
605
- return result;
635
+ return false;
606
636
  }
607
637
 
608
- if (!idCheck(sID)) {
609
- if (!(oOptions instanceof Object)) {
610
- // overloading use case: play('mySound','/path/to/some.mp3');
638
+ if (!idCheck(sID, overloaded)) {
639
+
640
+ if (!overloaded) {
641
+ // no sound found for the given ID. Bail.
642
+ return false;
643
+ }
644
+
645
+ if (overloaded) {
611
646
  oOptions = {
612
647
  url: oOptions
613
648
  };
614
649
  }
650
+
615
651
  if (oOptions && oOptions.url) {
616
- // overloading use case, create+play: .play('someID',{url:'/path/to.mp3'});
617
- sm2._wD(sm + '.play(): attempting to create "' + sID + '"', 1);
652
+ // overloading use case, create+play: .play('someID', {url:'/path/to.mp3'});
653
+ sm2._wD(sm + '.play(): Attempting to create "' + sID + '"', 1);
618
654
  oOptions.id = sID;
619
655
  result = sm2.createSound(oOptions).play();
620
656
  }
621
- return result;
657
+
658
+ } else if (overloaded) {
659
+
660
+ // existing sound object case
661
+ oOptions = {
662
+ url: oOptions
663
+ };
664
+
622
665
  }
623
666
 
624
- return sm2.sounds[sID].play(oOptions);
667
+ if (result === null) {
668
+ // default case
669
+ result = sm2.sounds[sID].play(oOptions);
670
+ }
671
+
672
+ return result;
625
673
 
626
674
  };
627
675
 
@@ -1020,14 +1068,14 @@ function SoundManager(smURL, smID) {
1020
1068
  this.getSoundById = function(sID, _suppressDebug) {
1021
1069
 
1022
1070
  if (!sID) {
1023
- throw new Error(sm + '.getSoundById(): sID is null/_undefined');
1071
+ return null;
1024
1072
  }
1025
1073
 
1026
1074
  var result = sm2.sounds[sID];
1027
1075
 
1028
1076
  // <d>
1029
1077
  if (!result && !_suppressDebug) {
1030
- sm2._wD('"' + sID + '" is an invalid sound ID.', 2);
1078
+ sm2._wD(sm + '.getSoundById(): Sound "' + sID + '" not found.', 2);
1031
1079
  }
1032
1080
  // </d>
1033
1081
 
@@ -1118,10 +1166,10 @@ function SoundManager(smURL, smID) {
1118
1166
  * Applies when debugMode = true
1119
1167
  *
1120
1168
  * @param {string} sText The console message
1121
- * @param {object} sType Optional string: Log type of 'info', 'warn' or 'error', or object (to be dumped)
1169
+ * @param {object} nType Optional log level (number), or object. Number case: Log type/style where 0 = 'info', 1 = 'warn', 2 = 'error'. Object case: Object to be dumped.
1122
1170
  */
1123
1171
 
1124
- this._writeDebug = function(sText, sType) {
1172
+ this._writeDebug = function(sText, sTypeOrObject) {
1125
1173
 
1126
1174
  // pseudo-private console.log()-style output
1127
1175
  // <d>
@@ -1133,11 +1181,11 @@ function SoundManager(smURL, smID) {
1133
1181
  }
1134
1182
 
1135
1183
  if (hasConsole && sm2.useConsole) {
1136
- if (sType && typeof sType === 'object') {
1184
+ if (sTypeOrObject && typeof sTypeOrObject === 'object') {
1137
1185
  // object passed; dump to console.
1138
- console.log(sText, sType);
1139
- } else if (debugLevels[sType] !== _undefined) {
1140
- console[debugLevels[sType]](sText);
1186
+ console.log(sText, sTypeOrObject);
1187
+ } else if (debugLevels[sTypeOrObject] !== _undefined) {
1188
+ console[debugLevels[sTypeOrObject]](sText);
1141
1189
  } else {
1142
1190
  console.log(sText);
1143
1191
  }
@@ -1158,19 +1206,19 @@ function SoundManager(smURL, smID) {
1158
1206
  oItem.className = 'sm2-alt';
1159
1207
  }
1160
1208
 
1161
- if (sType === _undefined) {
1162
- sType = 0;
1209
+ if (sTypeOrObject === _undefined) {
1210
+ sTypeOrObject = 0;
1163
1211
  } else {
1164
- sType = parseInt(sType, 10);
1212
+ sTypeOrObject = parseInt(sTypeOrObject, 10);
1165
1213
  }
1166
1214
 
1167
1215
  oItem.appendChild(doc.createTextNode(sText));
1168
1216
 
1169
- if (sType) {
1170
- if (sType >= 2) {
1217
+ if (sTypeOrObject) {
1218
+ if (sTypeOrObject >= 2) {
1171
1219
  oItem.style.fontWeight = 'bold';
1172
1220
  }
1173
- if (sType === 3) {
1221
+ if (sTypeOrObject === 3) {
1174
1222
  oItem.style.color = '#ff3333';
1175
1223
  }
1176
1224
  }
@@ -1231,7 +1279,7 @@ function SoundManager(smURL, smID) {
1231
1279
 
1232
1280
  // <d>
1233
1281
  if (sm2.soundIDs.length) {
1234
- sm2._wD('Destroying ' + sm2.soundIDs.length + ' SMSound objects...');
1282
+ sm2._wD('Destroying ' + sm2.soundIDs.length + ' SMSound object' + (sm2.soundIDs.length !== 1 ? 's' : '') + '...');
1235
1283
  }
1236
1284
  // </d>
1237
1285
 
@@ -1241,7 +1289,7 @@ function SoundManager(smURL, smID) {
1241
1289
  sm2.sounds[sm2.soundIDs[i]].destruct();
1242
1290
  }
1243
1291
 
1244
- // trash ze flash
1292
+ // trash ze flash (remove from the DOM)
1245
1293
 
1246
1294
  if (flash) {
1247
1295
 
@@ -1253,8 +1301,6 @@ function SoundManager(smURL, smID) {
1253
1301
 
1254
1302
  oRemoved = flash.parentNode.removeChild(flash);
1255
1303
 
1256
- _wDS('flRemoved');
1257
-
1258
1304
  } catch(e) {
1259
1305
 
1260
1306
  // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.
@@ -1274,6 +1320,8 @@ function SoundManager(smURL, smID) {
1274
1320
  sm2.soundIDs = [];
1275
1321
  sm2.sounds = {};
1276
1322
 
1323
+ idCounter = 0;
1324
+
1277
1325
  if (!resetEvents) {
1278
1326
  // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init
1279
1327
  for (i in on_queue) {
@@ -1404,7 +1452,7 @@ function SoundManager(smURL, smID) {
1404
1452
 
1405
1453
  SMSound = function(oOptions) {
1406
1454
 
1407
- var s = this, resetProperties, add_html5_events, remove_html5_events, stop_html5_timer, start_html5_timer, attachOnPosition, onplay_called = false, onPositionItems = [], onPositionFired = 0, detachOnPosition, applyFromTo, lastURL = null, lastHTML5State;
1455
+ var s = this, resetProperties, add_html5_events, remove_html5_events, stop_html5_timer, start_html5_timer, attachOnPosition, onplay_called = false, onPositionItems = [], onPositionFired = 0, detachOnPosition, applyFromTo, lastURL = null, lastHTML5State, urlOmitted;
1408
1456
 
1409
1457
  lastHTML5State = {
1410
1458
  // tracks duration + position (time)
@@ -1436,6 +1484,9 @@ function SoundManager(smURL, smID) {
1436
1484
  // internal HTML5 Audio() object reference
1437
1485
  this._a = null;
1438
1486
 
1487
+ // for flash 8 special-case createSound() without url, followed by load/play with url case
1488
+ urlOmitted = (this.url ? false : true);
1489
+
1439
1490
  /**
1440
1491
  * SMSound() public methods
1441
1492
  * ------------------------
@@ -1492,12 +1543,26 @@ function SoundManager(smURL, smID) {
1492
1543
 
1493
1544
  sm2._wD(s.id + ': load (' + instanceOptions.url + ')');
1494
1545
 
1546
+ if (!instanceOptions.url && !s.url) {
1547
+ sm2._wD(s.id + ': load(): url is unassigned. Exiting.', 2);
1548
+ return s;
1549
+ }
1550
+
1551
+ // <d>
1552
+ if (!s.isHTML5 && fV === 8 && !s.url && !instanceOptions.autoPlay) {
1553
+ // flash 8 load() -> play() won't work before onload has fired.
1554
+ sm2._wD(s.id + ': Flash 8 load() limitation: Wait for onload() before calling play().', 1);
1555
+ }
1556
+ // </d>
1557
+
1495
1558
  if (instanceOptions.url === s.url && s.readyState !== 0 && s.readyState !== 2) {
1496
1559
  _wDS('onURL', 1);
1497
1560
  // if loaded and an onload() exists, fire immediately.
1498
1561
  if (s.readyState === 3 && instanceOptions.onload) {
1499
1562
  // assume success based on truthy duration.
1500
- instanceOptions.onload.apply(s, [(!!s.duration)]);
1563
+ wrapCallback(s, function() {
1564
+ instanceOptions.onload.apply(s, [(!!s.duration)]);
1565
+ });
1501
1566
  }
1502
1567
  return s;
1503
1568
  }
@@ -1541,7 +1606,8 @@ function SoundManager(smURL, smID) {
1541
1606
  // early HTML5 implementation (non-standard)
1542
1607
  s._a.autobuffer = 'auto';
1543
1608
 
1544
- // standard
1609
+ // standard property, values: none / metadata / auto
1610
+ // reference: http://msdn.microsoft.com/en-us/library/ie/ff974759%28v=vs.85%29.aspx
1545
1611
  s._a.preload = 'auto';
1546
1612
 
1547
1613
  s._a._called_load = true;
@@ -1558,6 +1624,17 @@ function SoundManager(smURL, smID) {
1558
1624
 
1559
1625
  } else {
1560
1626
 
1627
+ if (sm2.html5Only) {
1628
+ sm2._wD(s.id + ': No flash support. Exiting.');
1629
+ return s;
1630
+ }
1631
+
1632
+ if (s._iO.url && s._iO.url.match(/data\:/i)) {
1633
+ // data: URIs not supported by Flash, either.
1634
+ sm2._wD(s.id + ': data: URIs not supported via Flash. Exiting.');
1635
+ return s;
1636
+ }
1637
+
1561
1638
  try {
1562
1639
  s.isHTML5 = false;
1563
1640
  s._iO = policyFix(loopFix(instanceOptions));
@@ -1614,10 +1691,9 @@ function SoundManager(smURL, smID) {
1614
1691
  if (s._a) {
1615
1692
 
1616
1693
  s._a.pause();
1617
- html5Unload(s._a, emptyURL);
1618
1694
 
1619
1695
  // update empty URL, too
1620
- lastURL = emptyURL;
1696
+ lastURL = html5Unload(s._a);
1621
1697
 
1622
1698
  }
1623
1699
 
@@ -1667,7 +1743,6 @@ function SoundManager(smURL, smID) {
1667
1743
  if (!_bFromSM) {
1668
1744
  // ensure deletion from controller
1669
1745
  sm2.destroySound(s.id, true);
1670
-
1671
1746
  }
1672
1747
 
1673
1748
  };
@@ -1681,7 +1756,9 @@ function SoundManager(smURL, smID) {
1681
1756
 
1682
1757
  this.play = function(oOptions, _updatePlayState) {
1683
1758
 
1684
- var fN, allowMulti, a, onready, startOK = true,
1759
+ var fN, allowMulti, a, onready,
1760
+ audioClone, onended, oncanplay,
1761
+ startOK = true,
1685
1762
  exit = null;
1686
1763
 
1687
1764
  // <d>
@@ -1711,7 +1788,7 @@ function SoundManager(smURL, smID) {
1711
1788
  s.instanceOptions = s._iO;
1712
1789
 
1713
1790
  // RTMP-only
1714
- if (s._iO.serverURL && !s.connected) {
1791
+ if (!s.isHTML5 && s._iO.serverURL && !s.connected) {
1715
1792
  if (!s.getAutoPlay()) {
1716
1793
  sm2._wD(fN +' Netstream not connected yet - setting autoPlay');
1717
1794
  s.setAutoPlay(true);
@@ -1729,6 +1806,10 @@ function SoundManager(smURL, smID) {
1729
1806
  allowMulti = s._iO.multiShot;
1730
1807
  if (!allowMulti) {
1731
1808
  sm2._wD(fN + 'Already playing (one-shot)', 1);
1809
+ if (s.isHTML5) {
1810
+ // go back to original position.
1811
+ s.setPosition(s._iO.position);
1812
+ }
1732
1813
  exit = s;
1733
1814
  } else {
1734
1815
  sm2._wD(fN + 'Already playing (multi-shot)', 1);
@@ -1741,8 +1822,19 @@ function SoundManager(smURL, smID) {
1741
1822
 
1742
1823
  // edge case: play() with explicit URL parameter
1743
1824
  if (oOptions.url && oOptions.url !== s.url) {
1744
- // load using merged options
1745
- s.load(s._iO);
1825
+
1826
+ // special case for createSound() followed by load() / play() with url; avoid double-load case.
1827
+ if (!s.readyState && !s.isHTML5 && fV === 8 && urlOmitted) {
1828
+
1829
+ urlOmitted = false;
1830
+
1831
+ } else {
1832
+
1833
+ // load using merged options
1834
+ s.load(s._iO);
1835
+
1836
+ }
1837
+
1746
1838
  }
1747
1839
 
1748
1840
  if (!s.loaded) {
@@ -1752,13 +1844,22 @@ function SoundManager(smURL, smID) {
1752
1844
  sm2._wD(fN + 'Attempting to load');
1753
1845
 
1754
1846
  // try to get this sound playing ASAP
1755
- if (!s.isHTML5) {
1756
- // assign directly because setAutoPlay() increments the instanceCount
1847
+ if (!s.isHTML5 && !sm2.html5Only) {
1848
+
1849
+ // flash: assign directly because setAutoPlay() increments the instanceCount
1757
1850
  s._iO.autoPlay = true;
1758
1851
  s.load(s._iO);
1759
- } else {
1852
+
1853
+ } else if (s.isHTML5) {
1854
+
1760
1855
  // iOS needs this when recycling sounds, loading a new URL on an existing object.
1761
1856
  s.load(s._iO);
1857
+
1858
+ } else {
1859
+
1860
+ sm2._wD(fN + 'Unsupported type. Exiting.');
1861
+ exit = s;
1862
+
1762
1863
  }
1763
1864
 
1764
1865
  // HTML5 hack - re-set instanceOptions?
@@ -1777,7 +1878,8 @@ function SoundManager(smURL, smID) {
1777
1878
 
1778
1879
  } else {
1779
1880
 
1780
- sm2._wD(fN);
1881
+ // "play()"
1882
+ sm2._wD(fN.substr(0, fN.lastIndexOf(':')));
1781
1883
 
1782
1884
  }
1783
1885
 
@@ -1857,9 +1959,10 @@ function SoundManager(smURL, smID) {
1857
1959
 
1858
1960
  }
1859
1961
 
1860
- sm2._wD(fN + 'Starting to play');
1962
+ // sm2._wD(fN + 'Starting to play');
1861
1963
 
1862
- if (!s.instanceCount || s._iO.multiShotEvents || (!s.isHTML5 && fV > 8 && !s.getAutoPlay())) {
1964
+ // increment instance counter, where enabled + supported
1965
+ if (!s.instanceCount || s._iO.multiShotEvents || (s.isHTML5 && s._iO.multiShot && !useGlobalHTML5Audio) || (!s.isHTML5 && fV > 8 && !s.getAutoPlay())) {
1863
1966
  s.instanceCount++;
1864
1967
  }
1865
1968
 
@@ -1887,13 +1990,13 @@ function SoundManager(smURL, smID) {
1887
1990
 
1888
1991
  if (!s.isHTML5) {
1889
1992
 
1890
- startOK = flash._start(s.id, s._iO.loops || 1, (fV === 9 ? s._iO.position : s._iO.position / 1000), s._iO.multiShot);
1993
+ startOK = flash._start(s.id, s._iO.loops || 1, (fV === 9 ? s.position : s.position / msecScale), s._iO.multiShot || false);
1891
1994
 
1892
1995
  if (fV === 9 && !startOK) {
1893
1996
  // edge case: no sound hardware, or 32-channel flash ceiling hit.
1894
1997
  // applies only to Flash 9, non-NetStream/MovieStar sounds.
1895
1998
  // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
1896
- sm2._wD(fN + 'No sound hardware, or 32-sound ceiling hit');
1999
+ sm2._wD(fN + 'No sound hardware, or 32-sound ceiling hit', 2);
1897
2000
  if (s._iO.onplayerror) {
1898
2001
  s._iO.onplayerror.apply(s);
1899
2002
  }
@@ -1902,13 +2005,56 @@ function SoundManager(smURL, smID) {
1902
2005
 
1903
2006
  } else {
1904
2007
 
1905
- start_html5_timer();
2008
+ if (s.instanceCount < 2) {
2009
+
2010
+ // HTML5 single-instance case
2011
+
2012
+ start_html5_timer();
2013
+
2014
+ a = s._setup_html5();
2015
+
2016
+ s.setPosition(s._iO.position);
2017
+
2018
+ a.play();
2019
+
2020
+ } else {
1906
2021
 
1907
- a = s._setup_html5();
2022
+ // HTML5 multi-shot case
1908
2023
 
1909
- s.setPosition(s._iO.position);
2024
+ sm2._wD(s.id + ': Cloning Audio() for instance #' + s.instanceCount + '...');
1910
2025
 
1911
- a.play();
2026
+ audioClone = new Audio(s._iO.url);
2027
+
2028
+ onended = function() {
2029
+ event.remove(audioClone, 'onended', onended);
2030
+ s._onfinish(s);
2031
+ // cleanup
2032
+ html5Unload(audioClone);
2033
+ audioClone = null;
2034
+ };
2035
+
2036
+ oncanplay = function() {
2037
+ event.remove(audioClone, 'canplay', oncanplay);
2038
+ try {
2039
+ audioClone.currentTime = s._iO.position/msecScale;
2040
+ } catch(err) {
2041
+ complain(s.id + ': multiShot play() failed to apply position of ' + (s._iO.position/msecScale));
2042
+ }
2043
+ audioClone.play();
2044
+ };
2045
+
2046
+ event.add(audioClone, 'ended', onended);
2047
+
2048
+ if (s._iO.position) {
2049
+ // HTML5 audio can't seek before onplay() event has fired.
2050
+ // wait for canplay, then seek to position and start playback.
2051
+ event.add(audioClone, 'canplay', oncanplay);
2052
+ } else {
2053
+ // begin playback at currentTime: 0
2054
+ audioClone.play();
2055
+ }
2056
+
2057
+ }
1912
2058
 
1913
2059
  }
1914
2060
 
@@ -2051,21 +2197,20 @@ function SoundManager(smURL, smID) {
2051
2197
  nMsecOffset = 0;
2052
2198
  }
2053
2199
 
2054
- var original_pos,
2055
- position, position1K,
2200
+ var position, position1K,
2056
2201
  // Use the duration from the instance options, if we don't have a track duration yet.
2057
2202
  // position >= 0 and <= current available (loaded) duration
2058
2203
  offset = (s.isHTML5 ? Math.max(nMsecOffset, 0) : Math.min(s.duration || s._iO.duration, Math.max(nMsecOffset, 0)));
2059
2204
 
2060
- original_pos = s.position;
2061
2205
  s.position = offset;
2062
- position1K = s.position/1000;
2206
+ position1K = s.position/msecScale;
2063
2207
  s._resetOnPosition(s.position);
2064
2208
  s._iO.position = offset;
2065
2209
 
2066
2210
  if (!s.isHTML5) {
2067
2211
 
2068
2212
  position = (fV === 9 ? s.position : position1K);
2213
+
2069
2214
  if (s.readyState && s.readyState !== 2) {
2070
2215
  // if paused or not playing, will not resume (by playing)
2071
2216
  flash._setPosition(s.id, position, (s.paused || !s.playState), s._iO.multiShot);
@@ -2075,13 +2220,16 @@ function SoundManager(smURL, smID) {
2075
2220
 
2076
2221
  // Set the position in the canplay handler if the sound is not ready yet
2077
2222
  if (s._html5_canplay) {
2223
+
2078
2224
  if (s._a.currentTime !== position1K) {
2225
+
2079
2226
  /**
2080
2227
  * DOM/JS errors/exceptions to watch out for:
2081
2228
  * if seek is beyond (loaded?) position, "DOM exception 11"
2082
2229
  * "INDEX_SIZE_ERR": DOM exception 1
2083
2230
  */
2084
2231
  sm2._wD(s.id + ': setPosition('+position1K+')');
2232
+
2085
2233
  try {
2086
2234
  s._a.currentTime = position1K;
2087
2235
  if (s.playState === 0 || s.paused) {
@@ -2091,19 +2239,25 @@ function SoundManager(smURL, smID) {
2091
2239
  } catch(e) {
2092
2240
  sm2._wD(s.id + ': setPosition(' + position1K + ') failed: ' + e.message, 2);
2093
2241
  }
2242
+
2094
2243
  }
2095
- } else {
2096
- sm2._wD(s.id + ': setPosition(' + position1K + '): Cannot seek yet, sound not ready');
2097
- }
2098
2244
 
2099
- }
2245
+ } else if (position1K) {
2246
+
2247
+ // warn on non-zero seek attempts
2248
+ sm2._wD(s.id + ': setPosition(' + position1K + '): Cannot seek yet, sound not ready', 2);
2249
+ return s;
2250
+
2251
+ }
2100
2252
 
2101
- if (s.isHTML5) {
2102
2253
  if (s.paused) {
2254
+
2103
2255
  // if paused, refresh UI right away
2104
2256
  // force update
2105
2257
  s._onTimer(true);
2258
+
2106
2259
  }
2260
+
2107
2261
  }
2108
2262
 
2109
2263
  return s;
@@ -2203,7 +2357,7 @@ function SoundManager(smURL, smID) {
2203
2357
 
2204
2358
  if (s.playState === 0) {
2205
2359
  s.play({
2206
- position: (fV === 9 && !s.isHTML5 ? s.position : s.position / 1000)
2360
+ position: (fV === 9 && !s.isHTML5 ? s.position : s.position / msecScale)
2207
2361
  });
2208
2362
  return s;
2209
2363
  }
@@ -2647,7 +2801,7 @@ function SoundManager(smURL, smID) {
2647
2801
  // TODO: investigate why this goes wack if not set/re-set each time.
2648
2802
  s.durationEstimate = s.duration;
2649
2803
 
2650
- time = (s._a.currentTime * 1000 || 0);
2804
+ time = (s._a.currentTime * msecScale || 0);
2651
2805
 
2652
2806
  if (time !== lastHTML5State.time) {
2653
2807
 
@@ -2680,7 +2834,7 @@ function SoundManager(smURL, smID) {
2680
2834
 
2681
2835
  var instanceOptions = s._iO,
2682
2836
  // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null
2683
- d = (s._a && s._a.duration ? s._a.duration*1000 : (instanceOptions && instanceOptions.duration ? instanceOptions.duration : null)),
2837
+ d = (s._a && s._a.duration ? s._a.duration*msecScale : (instanceOptions && instanceOptions.duration ? instanceOptions.duration : null)),
2684
2838
  result = (d && !isNaN(d) && d !== Infinity ? d : null);
2685
2839
 
2686
2840
  return result;
@@ -2706,9 +2860,9 @@ function SoundManager(smURL, smID) {
2706
2860
 
2707
2861
  this._setup_html5 = function(oOptions) {
2708
2862
 
2709
- var instanceOptions = mixin(s._iO, oOptions), d = decodeURI,
2710
- a = useGlobalHTML5Audio ? globalHTML5Audio : s._a,
2711
- dURL = d(instanceOptions.url),
2863
+ var instanceOptions = mixin(s._iO, oOptions),
2864
+ a = useGlobalHTML5Audio ? globalHTML5Audio : s._a,
2865
+ dURL = decodeURI(instanceOptions.url),
2712
2866
  sameURL;
2713
2867
 
2714
2868
  /**
@@ -2719,12 +2873,12 @@ function SoundManager(smURL, smID) {
2719
2873
 
2720
2874
  if (useGlobalHTML5Audio) {
2721
2875
 
2722
- if (dURL === lastGlobalHTML5URL) {
2876
+ if (dURL === decodeURI(lastGlobalHTML5URL)) {
2723
2877
  // global HTML5 audio: re-use of URL
2724
2878
  sameURL = true;
2725
2879
  }
2726
2880
 
2727
- } else if (dURL === lastURL) {
2881
+ } else if (dURL === decodeURI(lastURL)) {
2728
2882
 
2729
2883
  // options URL is the same as the "last" URL, and we used (loaded) it
2730
2884
  sameURL = true;
@@ -2744,7 +2898,7 @@ function SoundManager(smURL, smID) {
2744
2898
 
2745
2899
  }
2746
2900
 
2747
- } else if (!useGlobalHTML5Audio && dURL === d(lastURL)) {
2901
+ } else if (!useGlobalHTML5Audio && dURL === decodeURI(lastURL)) {
2748
2902
 
2749
2903
  // non-global HTML5 reuse case: same url, ignore request
2750
2904
  s._apply_loop(a, instanceOptions.loops);
@@ -2889,7 +3043,7 @@ function SoundManager(smURL, smID) {
2889
3043
 
2890
3044
  // <d>
2891
3045
  fN = s.id + ': ';
2892
- sm2._wD(fN + (loadOK ? 'onload()' : 'Failed to load? - ' + s.url), (loadOK ? 1 : 2));
3046
+ sm2._wD(fN + (loadOK ? 'onload()' : 'Failed to load / invalid sound?' + (!s.duration ? ' Zero-length duration reported.' : ' -') + ' (' + s.url + ')'), (loadOK ? 1 : 2));
2893
3047
  if (!loadOK && !s.isHTML5) {
2894
3048
  if (sm2.sandbox.noRemote === true) {
2895
3049
  sm2._wD(fN + str('noNet'), 1);
@@ -2905,7 +3059,9 @@ function SoundManager(smURL, smID) {
2905
3059
  s._onbufferchange(0);
2906
3060
 
2907
3061
  if (s._iO.onload) {
2908
- s._iO.onload.apply(s, [loadOK]);
3062
+ wrapCallback(s, function() {
3063
+ s._iO.onload.apply(s, [loadOK]);
3064
+ });
2909
3065
  }
2910
3066
 
2911
3067
  return true;
@@ -3004,7 +3160,9 @@ function SoundManager(smURL, smID) {
3004
3160
  // fire onfinish for last, or every instance
3005
3161
  if (io_onfinish) {
3006
3162
  sm2._wD(s.id + ': onfinish()');
3007
- io_onfinish.apply(s);
3163
+ wrapCallback(s, function() {
3164
+ io_onfinish.apply(s);
3165
+ });
3008
3166
  }
3009
3167
  }
3010
3168
 
@@ -3278,6 +3436,23 @@ function SoundManager(smURL, smID) {
3278
3436
 
3279
3437
  };
3280
3438
 
3439
+ wrapCallback = function(oSound, callback) {
3440
+
3441
+ /**
3442
+ * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue
3443
+ * setTimeout() fix for certain SMSound callbacks like onload() and onfinish(), where subsequent calls like play() and load() fail when Flash Player 11.6.602.171 is installed, and using soundManager with flashVersion = 8 (which is the default).
3444
+ * Not sure of exact cause. Suspect race condition and/or invalid (NaN-style) position argument trickling down to the next JS -> Flash _start() call, in the play() case.
3445
+ * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.
3446
+ * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player
3447
+ */
3448
+ if (!oSound.isHTML5 && fV === 8) {
3449
+ window.setTimeout(callback, 0);
3450
+ } else {
3451
+ callback();
3452
+ }
3453
+
3454
+ };
3455
+
3281
3456
  // additional soundManager properties that soundManager.setup() will accept
3282
3457
 
3283
3458
  extraOptions = {
@@ -3562,7 +3737,7 @@ function SoundManager(smURL, smID) {
3562
3737
  s._onbufferchange(0);
3563
3738
 
3564
3739
  // position according to instance options
3565
- position1K = (s._iO.position !== _undefined && !isNaN(s._iO.position)?s._iO.position/1000:null);
3740
+ position1K = (s._iO.position !== _undefined && !isNaN(s._iO.position)?s._iO.position/msecScale:null);
3566
3741
 
3567
3742
  // set the position if position was set before the sound loaded
3568
3743
  if (s.position && this.currentTime !== position1K) {
@@ -3615,6 +3790,14 @@ function SoundManager(smURL, smID) {
3615
3790
  error: html5_event(function() {
3616
3791
 
3617
3792
  sm2._wD(this._s.id + ': HTML5 error, code ' + this.error.code);
3793
+ /**
3794
+ * HTML5 error codes, per W3C
3795
+ * Error 1: Client aborted download at user's request.
3796
+ * Error 2: Network error after load started.
3797
+ * Error 3: Decoding issue.
3798
+ * Error 4: Media (audio file) not supported.
3799
+ * Reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
3800
+ */
3618
3801
  // call load with error state?
3619
3802
  this._s._onload(false);
3620
3803
 
@@ -3649,7 +3832,7 @@ function SoundManager(smURL, smID) {
3649
3832
 
3650
3833
  play: html5_event(function() {
3651
3834
 
3652
- sm2._wD(this._s.id + ': play()');
3835
+ // sm2._wD(this._s.id + ': play()');
3653
3836
  // once play starts, no buffering
3654
3837
  this._s._onbufferchange(0);
3655
3838
 
@@ -3673,9 +3856,7 @@ function SoundManager(smURL, smID) {
3673
3856
  ranges = e.target.buffered,
3674
3857
  // firefox 3.6 implements e.loaded/total (bytes)
3675
3858
  loaded = (e.loaded||0),
3676
- total = (e.total||1),
3677
- // HTML5 returns msec. SM2 API uses seconds for setPosition() etc., whether Flash or HTML5.
3678
- scale = 1000;
3859
+ total = (e.total||1);
3679
3860
 
3680
3861
  // reset the "buffered" (loaded byte ranges) array
3681
3862
  s.buffered = [];
@@ -3686,25 +3867,26 @@ function SoundManager(smURL, smID) {
3686
3867
  // https://developer.mozilla.org/en/DOM/TimeRanges
3687
3868
 
3688
3869
  // re-build "buffered" array
3870
+ // HTML5 returns seconds. SM2 API uses msec for setPosition() etc., whether Flash or HTML5.
3689
3871
  for (i=0, j=ranges.length; i<j; i++) {
3690
3872
  s.buffered.push({
3691
- 'start': ranges.start(i) * scale,
3692
- 'end': ranges.end(i) * scale
3873
+ 'start': ranges.start(i) * msecScale,
3874
+ 'end': ranges.end(i) * msecScale
3693
3875
  });
3694
3876
  }
3695
3877
 
3696
3878
  // use the last value locally
3697
- buffered = (ranges.end(0) - ranges.start(0)) * scale;
3879
+ buffered = (ranges.end(0) - ranges.start(0)) * msecScale;
3698
3880
 
3699
3881
  // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
3700
- loaded = buffered/(e.target.duration*scale);
3882
+ loaded = Math.min(1, buffered/(e.target.duration*msecScale));
3701
3883
 
3702
3884
  // <d>
3703
3885
  if (isProgress && ranges.length > 1) {
3704
3886
  str = [];
3705
3887
  j = ranges.length;
3706
3888
  for (i=0; i<j; i++) {
3707
- str.push(e.target.buffered.start(i)*scale +'-'+ e.target.buffered.end(i)*scale);
3889
+ str.push(e.target.buffered.start(i)*msecScale +'-'+ e.target.buffered.end(i)*msecScale);
3708
3890
  }
3709
3891
  sm2._wD(this._s.id + ': progress, timeRanges: ' + str.join(', '));
3710
3892
  }
@@ -3780,15 +3962,20 @@ function SoundManager(smURL, smID) {
3780
3962
 
3781
3963
  var result;
3782
3964
 
3783
- if (iO.serverURL || (iO.type && preferFlashCheck(iO.type))) {
3965
+ if (!iO || (!iO.type && !iO.url && !iO.serverURL)) {
3966
+
3967
+ // nothing to check
3968
+ result = false;
3969
+
3970
+ } else if (iO.serverURL || (iO.type && preferFlashCheck(iO.type))) {
3784
3971
 
3785
3972
  // RTMP, or preferring flash
3786
3973
  result = false;
3787
3974
 
3788
3975
  } else {
3789
3976
 
3790
- // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
3791
- result = ((iO.type ? html5CanPlay({type:iO.type}) : html5CanPlay({url:iO.url}) || sm2.html5Only));
3977
+ // Use type, if specified. Pass data: URIs to HTML5. If HTML5-only mode, no other options, so just give 'er
3978
+ result = ((iO.type ? html5CanPlay({type:iO.type}) : html5CanPlay({url:iO.url}) || sm2.html5Only || iO.url.match(/data\:/i)));
3792
3979
 
3793
3980
  }
3794
3981
 
@@ -3796,7 +3983,7 @@ function SoundManager(smURL, smID) {
3796
3983
 
3797
3984
  };
3798
3985
 
3799
- html5Unload = function(oAudio, url) {
3986
+ html5Unload = function(oAudio) {
3800
3987
 
3801
3988
  /**
3802
3989
  * Internal method: Unload media, and cancel any current/pending network requests.
@@ -3806,13 +3993,19 @@ function SoundManager(smURL, smID) {
3806
3993
  * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
3807
3994
  */
3808
3995
 
3996
+ var url;
3997
+
3809
3998
  if (oAudio) {
3810
3999
 
3811
- // Firefox likes '' for unload (used to work?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
4000
+ // Firefox likes '' for unload (used to work, but no longer?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
4001
+ url = (isSafari && !is_iDevice ? null : (isFirefox ? emptyURL : null));
4002
+
3812
4003
  oAudio.src = url;
3813
4004
 
3814
4005
  // reset some state, too
3815
- oAudio._called_load = false;
4006
+ if (oAudio._called_unload !== undefined) {
4007
+ oAudio._called_load = false;
4008
+ }
3816
4009
 
3817
4010
  }
3818
4011
 
@@ -3823,6 +4016,8 @@ function SoundManager(smURL, smID) {
3823
4016
 
3824
4017
  }
3825
4018
 
4019
+ return url;
4020
+
3826
4021
  };
3827
4022
 
3828
4023
  html5CanPlay = function(o) {
@@ -3906,6 +4101,9 @@ function SoundManager(smURL, smID) {
3906
4101
  */
3907
4102
 
3908
4103
  if (!sm2.useHTML5Audio || !sm2.hasHTML5) {
4104
+ // without HTML5, we need Flash.
4105
+ sm2.html5.usingFlash = true;
4106
+ needsFlash = true;
3909
4107
  return false;
3910
4108
  }
3911
4109
 
@@ -3993,6 +4191,9 @@ function SoundManager(smURL, smID) {
3993
4191
  support.canPlayType = (a?cp:null);
3994
4192
  sm2.html5 = mixin(sm2.html5, support);
3995
4193
 
4194
+ sm2.html5.usingFlash = featureCheck();
4195
+ needsFlash = sm2.html5.usingFlash;
4196
+
3996
4197
  return true;
3997
4198
 
3998
4199
  };
@@ -4012,7 +4213,7 @@ function SoundManager(smURL, smID) {
4012
4213
  waitForever: smc + 'Waiting indefinitely for Flash (will recover if unblocked)...',
4013
4214
  waitSWF: smc + 'Waiting for 100% SWF load...',
4014
4215
  needFunction: smc + 'Function object expected for %s',
4015
- badID: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',
4216
+ badID: 'Sound ID "%s" should be a string, starting with a non-numeric character',
4016
4217
  currentObj: smc + '_debug(): Current sound objects',
4017
4218
  waitOnload: smc + 'Waiting for window.onload()',
4018
4219
  docLoaded: smc + 'Document already loaded',
@@ -4026,7 +4227,6 @@ function SoundManager(smURL, smID) {
4026
4227
  smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
4027
4228
  fbTimeout: 'No flash response, applying .'+swfCSS.swfTimedout+' CSS...',
4028
4229
  fbLoaded: 'Flash loaded',
4029
- flRemoved: smc + 'Flash movie removed.',
4030
4230
  fbHandler: smc + 'flashBlockHandler()',
4031
4231
  manURL: 'SMSound.load(): Using manually-assigned URL',
4032
4232
  onURL: sm + '.load(): current URL already assigned.',
@@ -4101,7 +4301,7 @@ function SoundManager(smURL, smID) {
4101
4301
  complain = function(sMsg) {
4102
4302
 
4103
4303
  // <d>
4104
- if (console !== _undefined && console.warn !== _undefined) {
4304
+ if (hasConsole && console.warn !== _undefined) {
4105
4305
  console.warn(sMsg);
4106
4306
  } else {
4107
4307
  sm2._wD(sMsg);
@@ -4231,11 +4431,11 @@ function SoundManager(smURL, smID) {
4231
4431
  // starts debug mode, creating output <div> for UAs without console object
4232
4432
 
4233
4433
  // allow force of debug mode via URL
4434
+ // <d>
4234
4435
  if (sm2.debugURLParam.test(wl)) {
4235
4436
  sm2.debugMode = true;
4236
4437
  }
4237
4438
 
4238
- // <d>
4239
4439
  if (id(sm2.debugID)) {
4240
4440
  return false;
4241
4441
  }
@@ -4339,6 +4539,7 @@ function SoundManager(smURL, smID) {
4339
4539
  sm2Debugger.handleEvent(sEventType, bSuccess, sMessage);
4340
4540
  } catch(e) {
4341
4541
  // oh well
4542
+ return false;
4342
4543
  }
4343
4544
  }
4344
4545
 
@@ -4377,6 +4578,7 @@ function SoundManager(smURL, smID) {
4377
4578
  error = {type:'FLASHBLOCK'};
4378
4579
 
4379
4580
  if (sm2.html5Only) {
4581
+ // no flash, or unused
4380
4582
  return false;
4381
4583
  }
4382
4584
 
@@ -4535,6 +4737,7 @@ function SoundManager(smURL, smID) {
4535
4737
  obj = new AX('ShockwaveFlash.ShockwaveFlash');
4536
4738
  } catch(e) {
4537
4739
  // oh well
4740
+ obj = null;
4538
4741
  }
4539
4742
  hasPlugin = (!!obj);
4540
4743
  // cleanup, because it is ActiveX after all
@@ -4549,9 +4752,8 @@ function SoundManager(smURL, smID) {
4549
4752
 
4550
4753
  featureCheck = function() {
4551
4754
 
4552
- var needsFlash,
4755
+ var flashNeeded,
4553
4756
  item,
4554
- result = true,
4555
4757
  formats = sm2.audioFormats,
4556
4758
  // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
4557
4759
  isSpecial = (is_iDevice && !!(ua.match(/os (1|2|3_0|3_1)/i)));
@@ -4564,12 +4766,11 @@ function SoundManager(smURL, smID) {
4564
4766
  // ignore flash case, however
4565
4767
  sm2.html5Only = true;
4566
4768
 
4769
+ // hide the SWF, if present
4567
4770
  if (sm2.oMC) {
4568
4771
  sm2.oMC.style.display = 'none';
4569
4772
  }
4570
4773
 
4571
- result = false;
4572
-
4573
4774
  } else {
4574
4775
 
4575
4776
  if (sm2.useHTML5Audio) {
@@ -4591,11 +4792,22 @@ function SoundManager(smURL, smID) {
4591
4792
 
4592
4793
  if (sm2.useHTML5Audio && sm2.hasHTML5) {
4593
4794
 
4795
+ // sort out whether flash is optional, required or can be ignored.
4796
+
4797
+ // innocent until proven guilty.
4798
+ canIgnoreFlash = true;
4799
+
4594
4800
  for (item in formats) {
4595
4801
  if (formats.hasOwnProperty(item)) {
4596
- if ((formats[item].required && !sm2.html5.canPlayType(formats[item].type)) || (sm2.preferFlash && (sm2.flash[item] || sm2.flash[formats[item].type]))) {
4597
- // flash may be required, or preferred for this format
4598
- needsFlash = true;
4802
+ if (formats[item].required) {
4803
+ if (!sm2.html5.canPlayType(formats[item].type)) {
4804
+ // 100% HTML5 mode is not possible.
4805
+ canIgnoreFlash = false;
4806
+ flashNeeded = true;
4807
+ } else if (sm2.preferFlash && (sm2.flash[item] || sm2.flash[formats[item].type])) {
4808
+ // flash may be required, or preferred for this format.
4809
+ flashNeeded = true;
4810
+ }
4599
4811
  }
4600
4812
  }
4601
4813
  }
@@ -4604,10 +4816,11 @@ function SoundManager(smURL, smID) {
4604
4816
 
4605
4817
  // sanity check...
4606
4818
  if (sm2.ignoreFlash) {
4607
- needsFlash = false;
4819
+ flashNeeded = false;
4820
+ canIgnoreFlash = true;
4608
4821
  }
4609
4822
 
4610
- sm2.html5Only = (sm2.hasHTML5 && sm2.useHTML5Audio && !needsFlash);
4823
+ sm2.html5Only = (sm2.hasHTML5 && sm2.useHTML5Audio && !flashNeeded);
4611
4824
 
4612
4825
  return (!sm2.html5Only);
4613
4826
 
@@ -4675,7 +4888,7 @@ function SoundManager(smURL, smID) {
4675
4888
 
4676
4889
  if (h5IntervalTimer === null && h5TimerCount === 0) {
4677
4890
 
4678
- h5IntervalTimer = window.setInterval(timerExecute, sm2.html5PollingInterval);
4891
+ h5IntervalTimer = setInterval(timerExecute, sm2.html5PollingInterval);
4679
4892
 
4680
4893
  }
4681
4894
 
@@ -4721,7 +4934,7 @@ function SoundManager(smURL, smID) {
4721
4934
 
4722
4935
  // no active timers, stop polling interval.
4723
4936
 
4724
- window.clearInterval(h5IntervalTimer);
4937
+ clearInterval(h5IntervalTimer);
4725
4938
 
4726
4939
  h5IntervalTimer = null;
4727
4940
 
@@ -4818,10 +5031,9 @@ function SoundManager(smURL, smID) {
4818
5031
 
4819
5032
  };
4820
5033
 
4821
- this._externalInterfaceOK = function(flashDate, swfVersion) {
5034
+ this._externalInterfaceOK = function(swfVersion) {
4822
5035
 
4823
5036
  // flash callback confirming flash loaded, EI working etc.
4824
- // flashDate = approx. timing/delay info for JS/flash bridge
4825
5037
  // swfVersion: SWF build string
4826
5038
 
4827
5039
  if (sm2.swfLoaded) {
@@ -4856,7 +5068,7 @@ function SoundManager(smURL, smID) {
4856
5068
  }
4857
5069
  // </d>
4858
5070
 
4859
- // slight delay before init
5071
+ // IE needs a larger timeout
4860
5072
  setTimeout(init, isIE ? 100 : 1);
4861
5073
 
4862
5074
  };
@@ -5254,26 +5466,69 @@ function SoundManager(smURL, smID) {
5254
5466
  // give up / time-out, depending
5255
5467
 
5256
5468
  if (!didInit && okToDisable) {
5469
+
5257
5470
  if (p === null) {
5471
+
5258
5472
  // SWF failed. Maybe blocked.
5473
+
5259
5474
  if (sm2.useFlashBlock || sm2.flashLoadTimeout === 0) {
5475
+
5260
5476
  if (sm2.useFlashBlock) {
5477
+
5261
5478
  flashBlockHandler();
5479
+
5262
5480
  }
5481
+
5263
5482
  _wDS('waitForever');
5483
+
5264
5484
  } else {
5485
+
5265
5486
  // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.
5266
- _wDS('waitForever');
5267
- // fire any regular registered ontimeout() listeners.
5268
- processOnEvents({type:'ontimeout', ignoreInit: true});
5487
+
5488
+ if (!sm2.useFlashBlock && canIgnoreFlash) {
5489
+
5490
+ // special case: try for a reboot with preferFlash: false, if 100% HTML5 mode is possible and useFlashBlock is not enabled.
5491
+
5492
+ window.setTimeout(function() {
5493
+
5494
+ complain(smc + 'useFlashBlock is false, 100% HTML5 mode is possible. Rebooting with preferFlash: false...');
5495
+
5496
+ sm2.setup({
5497
+ preferFlash: false
5498
+ }).reboot();
5499
+
5500
+ // if for some reason you want to detect this case, use an ontimeout() callback and look for html5Only and didFlashBlock == true.
5501
+ sm2.didFlashBlock = true;
5502
+
5503
+ sm2.beginDelayedInit();
5504
+
5505
+ }, 1);
5506
+
5507
+ } else {
5508
+
5509
+ _wDS('waitForever');
5510
+
5511
+ // fire any regular registered ontimeout() listeners.
5512
+ processOnEvents({type:'ontimeout', ignoreInit: true});
5513
+
5514
+ }
5515
+
5269
5516
  }
5517
+
5270
5518
  } else {
5519
+
5271
5520
  // flash loaded? Shouldn't be a blocking issue, then.
5521
+
5272
5522
  if (sm2.flashLoadTimeout === 0) {
5273
- _wDS('waitForever');
5523
+
5524
+ _wDS('waitForever');
5525
+
5274
5526
  } else {
5527
+
5275
5528
  failSafely(true);
5529
+
5276
5530
  }
5531
+
5277
5532
  }
5278
5533
  }
5279
5534
 
@@ -5333,7 +5588,7 @@ function SoundManager(smURL, smID) {
5333
5588
  if (sm2.useHTML5Audio && sm2.hasHTML5) {
5334
5589
  for (item in sm2.audioFormats) {
5335
5590
  if (sm2.audioFormats.hasOwnProperty(item)) {
5336
- tests.push(item + ' = ' + sm2.html5[item] + (!sm2.html5[item] && hasFlash && sm2.flash[item] ? ' (using flash)' : (sm2.preferFlash && sm2.flash[item] && hasFlash ? ' (preferring flash)': (!sm2.html5[item] ? ' (' + (sm2.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));
5591
+ tests.push(item + ' = ' + sm2.html5[item] + (!sm2.html5[item] && needsFlash && sm2.flash[item] ? ' (using flash)' : (sm2.preferFlash && sm2.flash[item] && needsFlash ? ' (preferring flash)': (!sm2.html5[item] ? ' (' + (sm2.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));
5337
5592
  }
5338
5593
  }
5339
5594
  sm2._wD('SoundManager 2 HTML5 support: ' + tests.join(', '), 1);
@@ -5530,12 +5785,11 @@ function SoundManager(smURL, smID) {
5530
5785
  a2 = 'sm2-preferflash=',
5531
5786
  b = null,
5532
5787
  b2 = null,
5533
- hasCon = (window.console !== _undefined && typeof console.log === 'function'),
5534
5788
  l = wl.toLowerCase();
5535
5789
 
5536
5790
  if (l.indexOf(a) !== -1) {
5537
5791
  b = (l.charAt(l.indexOf(a)+a.length) === '1');
5538
- if (hasCon) {
5792
+ if (hasConsole) {
5539
5793
  console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
5540
5794
  }
5541
5795
  sm2.setup({
@@ -5545,7 +5799,7 @@ function SoundManager(smURL, smID) {
5545
5799
 
5546
5800
  if (l.indexOf(a2) !== -1) {
5547
5801
  b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');
5548
- if (hasCon) {
5802
+ if (hasConsole) {
5549
5803
  console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
5550
5804
  }
5551
5805
  sm2.setup({
@@ -5567,8 +5821,6 @@ function SoundManager(smURL, smID) {
5567
5821
  }
5568
5822
 
5569
5823
  testHTML5();
5570
- sm2.html5.usingFlash = featureCheck();
5571
- needsFlash = sm2.html5.usingFlash;
5572
5824
 
5573
5825
  if (!hasFlash && needsFlash) {
5574
5826
  messages.push(strings.needFlash);