soundmanager-rails 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);