vendor/assets/javascripts/soundmanager2.js in soundmanager-rails-0.1.0 vs vendor/assets/javascripts/soundmanager2.js in soundmanager-rails-0.1.1
- old
+ new
@@ -6,11 +6,11 @@
*
* Copyright (c) 2007, Scott Schiller. All rights reserved.
* Code provided under the BSD License:
* http://schillmania.com/projects/soundmanager2/license.txt
*
- * V2.97a.20120318
+ * V2.97a.20120624
*/
/*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio */
/*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true */
@@ -44,61 +44,38 @@
* @return {SoundManager} The new SoundManager instance
*/
function SoundManager(smURL, smID) {
- // Top-level configuration options
+ /**
+ * soundManager configuration options list
+ * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)
+ * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})
+ */
- this.flashVersion = 8; // flash build to use (8 or 9.) Some API features require 9.
- this.debugMode = true; // enable debugging output (console.log() with HTML fallback)
- this.debugFlash = false; // enable debugging output inside SWF, troubleshoot Flash/browser issues
- this.useConsole = true; // use console.log() if available (otherwise, writes to #soundmanager-debug element)
- this.consoleOnly = true; // if console is being used, do not create/write to #soundmanager-debug
- this.waitForWindowLoad = false; // force SM2 to wait for window.onload() before trying to call soundManager.onload()
- this.bgColor = '#ffffff'; // SWF background color. N/A when wmode = 'transparent'
- this.useHighPerformance = false; // position:fixed flash movie can help increase js/flash speed, minimize lag
- this.flashPollingInterval = null; // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
- this.html5PollingInterval = null; // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
- this.flashLoadTimeout = 1000; // msec to wait for flash movie to load before failing (0 = infinity)
- this.wmode = null; // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
- this.allowScriptAccess = 'always'; // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
- this.useFlashBlock = false; // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
- this.useHTML5Audio = true; // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
- this.html5Test = /^(probably|maybe)$/i; // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
- this.preferFlash = true; // overrides useHTML5audio. if true and flash support present, will try to use flash for MP3/MP4 as needed since HTML5 audio support is still quirky in browsers.
- this.noSWFCache = false; // if true, appends ?ts={date} to break aggressive SWF caching.
+ this.setupOptions = {
- this.audioFormats = {
+ 'url': (smURL || null), // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/
+ 'flashVersion': 8, // flash build to use (8 or 9.) Some API features require 9.
+ 'debugMode': true, // enable debugging output (console.log() with HTML fallback)
+ 'debugFlash': false, // enable debugging output inside SWF, troubleshoot Flash/browser issues
+ 'useConsole': true, // use console.log() if available (otherwise, writes to #soundmanager-debug element)
+ 'consoleOnly': true, // if console is being used, do not create/write to #soundmanager-debug
+ 'waitForWindowLoad': false, // force SM2 to wait for window.onload() before trying to call soundManager.onload()
+ 'bgColor': '#ffffff', // SWF background color. N/A when wmode = 'transparent'
+ 'useHighPerformance': false, // position:fixed flash movie can help increase js/flash speed, minimize lag
+ 'flashPollingInterval': null, // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
+ 'html5PollingInterval': null, // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
+ 'flashLoadTimeout': 1000, // msec to wait for flash movie to load before failing (0 = infinity)
+ 'wmode': null, // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
+ 'allowScriptAccess': 'always', // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
+ 'useFlashBlock': false, // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
+ 'useHTML5Audio': true, // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
+ 'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
+ '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.
+ 'noSWFCache': false // if true, appends ?ts={date} to break aggressive SWF caching.
- /**
- * determines HTML5 support + flash requirements.
- * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
- * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
- * multiple MIME types may be tested while trying to get a positive canPlayType() response.
- */
-
- 'mp3': {
- 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
- 'required': true
- },
-
- 'mp4': {
- 'related': ['aac','m4a'], // additional formats under the MP4 container
- 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
- 'required': false
- },
-
- 'ogg': {
- 'type': ['audio/ogg; codecs=vorbis'],
- 'required': false
- },
-
- 'wav': {
- 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
- 'required': false
- }
-
};
this.defaultOptions = {
/**
@@ -161,24 +138,55 @@
'onconnect': null, // rtmp: callback for connection to flash media server
'duration': null // rtmp: song duration (msec)
};
+ this.audioFormats = {
+
+ /**
+ * determines HTML5 support + flash requirements.
+ * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
+ * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
+ * multiple MIME types may be tested while trying to get a positive canPlayType() response.
+ */
+
+ 'mp3': {
+ 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
+ 'required': true
+ },
+
+ 'mp4': {
+ 'related': ['aac','m4a'], // additional formats under the MP4 container
+ 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
+ 'required': false
+ },
+
+ 'ogg': {
+ 'type': ['audio/ogg; codecs=vorbis'],
+ 'required': false
+ },
+
+ 'wav': {
+ 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
+ 'required': false
+ }
+
+ };
+
// HTML attributes (id + class names) for the SWF container
this.movieID = 'sm2-container';
this.id = (smID || 'sm2movie');
this.debugID = 'soundmanager-debug';
this.debugURLParam = /([#?&])debug=1/i;
// dynamic attributes
- this.versionNumber = 'V2.97a.20120318';
+ this.versionNumber = 'V2.97a.20120624';
this.version = null;
this.movieURL = null;
- this.url = (smURL || null);
this.altURL = null;
this.swfLoaded = false;
this.enabled = false;
this.oMC = null;
this.sounds = {};
@@ -248,26 +256,30 @@
this.html5 = {
'usingFlash': null // set if/when flash fallback is needed
};
- this.flash = {}; // file type support hash
+ // file type support hash
+ this.flash = {};
- this.html5Only = false; // determined at init time
- this.ignoreFlash = false; // used for special cases (eg. iPad/iPhone/palm OS?)
+ // determined at init time
+ this.html5Only = false;
+ // used for special cases (eg. iPad/iPhone/palm OS?)
+ this.ignoreFlash = false;
+
/**
* a few private internals (OK, a lot. :D)
*/
var SMSound,
- _s = this, _flash = null, _sm = 'soundManager', _smc = _sm+'::', _h5 = 'HTML5::', _id, _ua = navigator.userAgent, _win = window, _wl = _win.location.href.toString(), _doc = document, _doNothing, _init, _fV, _on_queue = [], _debugOpen = true, _debugTS, _didAppend = false, _appendSuccess = false, _didInit = false, _disabled = false, _windowLoaded = false, _wDS, _wdCount = 0, _initComplete, _mixin, _addOnEvent, _processOnEvents, _initUserOnload, _delayWaitForEI, _waitForEI, _setVersionInfo, _handleFocus, _strings, _initMovie, _domContentLoaded, _winOnLoad, _didDCLoaded, _getDocument, _createMovie, _catchError, _setPolling, _initDebug, _debugLevels = ['log', 'info', 'warn', 'error'], _defaultFlashVersion = 8, _disableObject, _failSafely, _normalizeMovieURL, _oRemoved = null, _oRemovedHTML = null, _str, _flashBlockHandler, _getSWFCSS, _swfCSS, _toggleDebug, _loopFix, _policyFix, _complain, _idCheck, _waitingForEI = false, _initPending = false, _startTimer, _stopTimer, _timerExecute, _h5TimerCount = 0, _h5IntervalTimer = null, _parseURL,
+ _s = this, _flash = null, _sm = 'soundManager', _smc = _sm+'::', _h5 = 'HTML5::', _id, _ua = navigator.userAgent, _win = window, _wl = _win.location.href.toString(), _doc = document, _doNothing, _setProperties, _init, _fV, _on_queue = [], _debugOpen = true, _debugTS, _didAppend = false, _appendSuccess = false, _didInit = false, _disabled = false, _windowLoaded = false, _wDS, _wdCount = 0, _initComplete, _mixin, _assign, _extraOptions, _addOnEvent, _processOnEvents, _initUserOnload, _delayWaitForEI, _waitForEI, _setVersionInfo, _handleFocus, _strings, _initMovie, _domContentLoaded, _winOnLoad, _didDCLoaded, _getDocument, _createMovie, _catchError, _setPolling, _initDebug, _debugLevels = ['log', 'info', 'warn', 'error'], _defaultFlashVersion = 8, _disableObject, _failSafely, _normalizeMovieURL, _oRemoved = null, _oRemovedHTML = null, _str, _flashBlockHandler, _getSWFCSS, _swfCSS, _toggleDebug, _loopFix, _policyFix, _complain, _idCheck, _waitingForEI = false, _initPending = false, _startTimer, _stopTimer, _timerExecute, _h5TimerCount = 0, _h5IntervalTimer = null, _parseURL,
_needsFlash = null, _featureCheck, _html5OK, _html5CanPlay, _html5Ext, _html5Unload, _domContentLoadedIE, _testHTML5, _event, _slice = Array.prototype.slice, _useGlobalHTML5Audio = false, _hasFlash, _detectFlash, _badSafariFix, _html5_events, _showSupport,
- _is_iDevice = _ua.match(/(ipad|iphone|ipod)/i), _is_firefox = _ua.match(/firefox/i), _is_android = _ua.match(/droid/i), _isIE = _ua.match(/msie/i), _isWebkit = _ua.match(/webkit/i), _isSafari = (_ua.match(/safari/i) && !_ua.match(/chrome/i)), _isOpera = (_ua.match(/opera/i)),
- _likesHTML5 = (_ua.match(/(mobile|pre\/|xoom)/i) || _is_iDevice),
+ _is_iDevice = _ua.match(/(ipad|iphone|ipod)/i), _isIE = _ua.match(/msie/i), _isWebkit = _ua.match(/webkit/i), _isSafari = (_ua.match(/safari/i) && !_ua.match(/chrome/i)), _isOpera = (_ua.match(/opera/i)),
+ _mobileHTML5 = (_ua.match(/(mobile|pre\/|xoom)/i) || _is_iDevice),
_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
- _hasConsole = (typeof console !== 'undefined' && typeof console.log !== 'undefined'), _isFocused = (typeof _doc.hasFocus !== 'undefined'?_doc.hasFocus():null), _tryInitOnFocus = (_isSafari && typeof _doc.hasFocus === 'undefined'), _okToDisable = !_tryInitOnFocus, _flashMIME = /(mp3|mp4|mpa)/i,
+ _hasConsole = (typeof console !== 'undefined' && typeof console.log !== 'undefined'), _isFocused = (typeof _doc.hasFocus !== 'undefined'?_doc.hasFocus():null), _tryInitOnFocus = (_isSafari && (typeof _doc.hasFocus === 'undefined' || !_doc.hasFocus())), _okToDisable = !_tryInitOnFocus, _flashMIME = /(mp3|mp4|mpa|m4a)/i,
_emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
_overHTTP = (_doc.location?_doc.location.protocol.match(/http/i):null),
_http = (!_overHTTP ? 'http:/'+'/' : ''),
// mp3, mp4, aac etc.
_netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|mp4v|3gp|3g2)\s*(?:$|;)/i,
@@ -277,10 +289,11 @@
this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set
// use altURL if not "online"
this.useAltURL = !_overHTTP;
+
this._global_a = null;
_swfCSS = {
'swfBox': 'sm2-object-box',
@@ -293,11 +306,11 @@
'highPerf': 'high_performance',
'flashDebug': 'flash_debug'
};
- if (_likesHTML5) {
+ if (_mobileHTML5) {
// prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
_s.useHTML5Audio = true;
_s.preferFlash = false;
@@ -312,10 +325,33 @@
/**
* Public SoundManager API
* -----------------------
*/
+ /**
+ * Configures top-level soundManager properties.
+ *
+ * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
+ * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
+ */
+
+ this.setup = function(options) {
+
+ // warn if flash options have already been applied
+
+ if (typeof options !== 'undefined' && _didInit && _needsFlash && _s.ok() && (typeof options.flashVersion !== 'undefined' || typeof options.url !== 'undefined')) {
+ _complain(_str('setupLate'));
+ }
+
+ // TODO: defer: true?
+
+ _assign(options);
+
+ return _s;
+
+ };
+
this.ok = function() {
return (_needsFlash?(_didInit && !_disabled):(_s.useHTML5Audio && _s.hasHTML5));
};
@@ -330,18 +366,17 @@
};
/**
* Creates a SMSound sound object instance.
*
- * @param {object} oOptions Sound options (at minimum, id and url are required.)
+ * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
* @return {object} SMSound The new SMSound object.
*/
- this.createSound = function(oOptions) {
+ this.createSound = function(oOptions, _url) {
- var _cs, _cs_string,
- thisOptions = null, oSound = null, _tO = null;
+ var _cs, _cs_string, thisOptions = null, oSound = null, _tO = null;
// <d>
_cs = _sm+'.createSound(): ';
_cs_string = _cs + _str(!_didInit?'notReady':'notOK');
// </d>
@@ -349,15 +384,15 @@
if (!_didInit || !_s.ok()) {
_complain(_cs_string);
return false;
}
- if (arguments.length === 2) {
+ if (typeof _url !== 'undefined') {
// function overloading in JS! :) ..assume simple createSound(id,url) use case
oOptions = {
- 'id': arguments[0],
- 'url': arguments[1]
+ 'id': oOptions,
+ 'url': _url
};
}
// inherit from defaultOptions
thisOptions = _mixin(oOptions);
@@ -390,36 +425,28 @@
}
if (_html5OK(_tO)) {
oSound = make();
- _s._wD('Loading sound '+_tO.id+' via HTML5');
+ _s._wD('Creating sound '+_tO.id+', using HTML5');
oSound._setup_html5(_tO);
} else {
if (_fV > 8) {
if (_tO.isMovieStar === null) {
// attempt to detect MPEG-4 formats
- _tO.isMovieStar = (_tO.serverURL || (_tO.type ? _tO.type.match(_netStreamMimeTypes) : false) || _tO.url.match(_netStreamPattern));
+ _tO.isMovieStar = !!(_tO.serverURL || (_tO.type ? _tO.type.match(_netStreamMimeTypes) : false) || _tO.url.match(_netStreamPattern));
}
// <d>
if (_tO.isMovieStar) {
_s._wD(_cs + 'using MovieStar handling');
- }
- // </d>
- if (_tO.isMovieStar) {
- if (_tO.usePeakData) {
- _wDS('noPeak');
- _tO.usePeakData = false;
- }
- // <d>
if (_tO.loops > 1) {
_wDS('noNSLoop');
}
- // </d>
}
+ // </d>
}
_tO = _policyFix(_tO, _cs);
oSound = make();
@@ -572,13 +599,15 @@
* @return {SMSound} The SMSound object
*/
this.play = function(sID, oOptions) {
+ var result = false;
+
if (!_didInit || !_s.ok()) {
_complain(_sm+'.play(): ' + _str(!_didInit?'notReady':'notOK'));
- return false;
+ return result;
}
if (!_idCheck(sID)) {
if (!(oOptions instanceof Object)) {
// overloading use case: play('mySound','/path/to/some.mp3');
@@ -588,14 +617,13 @@
}
if (oOptions && oOptions.url) {
// overloading use case, create+play: .play('someID',{url:'/path/to.mp3'});
_s._wD(_sm+'.play(): attempting to create "' + sID + '"', 1);
oOptions.id = sID;
- return _s.createSound(oOptions).play();
- } else {
- return false;
+ result = _s.createSound(oOptions).play();
}
+ return result;
}
return _s.sounds[sID].play(oOptions);
};
@@ -928,18 +956,17 @@
if (_s.hasHTML5) {
result = _html5CanPlay({type:sMIME});
}
- if (!_needsFlash || result) {
- // no flash, or OK
- return result;
- } else {
+ if (!result && _needsFlash) {
// if flash 9, test netStream (movieStar) types as well.
- return (sMIME && _s.ok() ? !!((_fV > 8 ? sMIME.match(_netStreamMimeTypes) : null) || sMIME.match(_s.mimePattern)) : null);
+ result = (sMIME && _s.ok() ? !!((_fV > 8 ? sMIME.match(_netStreamMimeTypes) : null) || sMIME.match(_s.mimePattern)) : null);
}
+ return result;
+
};
/**
* Determines playability of a URL based on audio support.
*
@@ -953,17 +980,16 @@
if (_s.hasHTML5) {
result = _html5CanPlay({url: sURL});
}
- if (!_needsFlash || result) {
- // no flash, or OK
- return result;
- } else {
- return (sURL && _s.ok() ? !!(sURL.match(_s.filePattern)) : null);
+ if (!result && _needsFlash) {
+ result = (sURL && _s.ok() ? !!(sURL.match(_s.filePattern)) : null);
}
+ return result;
+
};
/**
* Determines playability of an HTML DOM <a> object (or similar object literal) based on audio support.
*
@@ -1015,13 +1041,14 @@
* @param {object} oScope Optional: The scope to apply to the callback
*/
this.onready = function(oMethod, oScope) {
- var sType = 'onready';
+ var sType = 'onready',
+ result = false;
- if (oMethod && oMethod instanceof Function) {
+ if (typeof oMethod === 'function') {
// <d>
if (_didInit) {
_s._wD(_str('queue', sType));
}
@@ -1032,18 +1059,20 @@
}
_addOnEvent(sType, oMethod, oScope);
_processOnEvents();
- return true;
+ result = true;
} else {
throw _str('needFunction', sType);
}
+ return result;
+
};
/**
* Queues a callback for execution when SoundManager has failed to initialize.
*
@@ -1051,13 +1080,14 @@
* @param {object} oScope Optional: The scope to apply to the callback
*/
this.ontimeout = function(oMethod, oScope) {
- var sType = 'ontimeout';
+ var sType = 'ontimeout',
+ result = false;
- if (oMethod && oMethod instanceof Function) {
+ if (typeof oMethod === 'function') {
// <d>
if (_didInit) {
_s._wD(_str('queue', sType));
}
@@ -1068,23 +1098,25 @@
}
_addOnEvent(sType, oMethod, oScope);
_processOnEvents({type:sType});
- return true;
+ result = true;
} else {
throw _str('needFunction', sType);
}
+ return result;
+
};
/**
* Writes console.log()-style debug output to a console or in-browser element.
- * Applies when SoundManager.debugMode = true
+ * Applies when debugMode = true
*
* @param {string} sText The console message
* @param {string} sType Optional: Log type of 'info', 'warn' or 'error'
* @param {object} Optional: The scope to apply to the callback
*/
@@ -1205,19 +1237,21 @@
_s.sounds[_s.soundIDs[i]].destruct();
}
// trash ze flash
- try {
- if (_isIE) {
- _oRemovedHTML = _flash.innerHTML;
+ if (_flash) {
+ try {
+ if (_isIE) {
+ _oRemovedHTML = _flash.innerHTML;
+ }
+ _oRemoved = _flash.parentNode.removeChild(_flash);
+ _s._wD('Flash movie removed.');
+ } catch(e) {
+ // uh-oh.
+ _wDS('badRemove', 2);
}
- _oRemoved = _flash.parentNode.removeChild(_flash);
- _s._wD('Flash movie removed.');
- } catch(e) {
- // uh-oh.
- _wDS('badRemove', 2);
}
// actually, force recreate of movie.
_oRemovedHTML = _oRemoved = _needsFlash = null;
@@ -1245,10 +1279,11 @@
* @return {number or null} Percent loaded, or if invalid/unsupported, null.
*/
this.getMoviePercent = function() {
+ // interesting note: flash/ExternalInterface bridge methods are not typeof "function" nor instanceof Function, but are still valid.
return (_flash && typeof _flash.PercentLoaded !== 'undefined' ? _flash.PercentLoaded() : null);
};
/**
@@ -1305,11 +1340,15 @@
// tracks duration + position (time)
duration: null,
time: null
};
- this.sID = oOptions.id;
+ this.id = oOptions.id;
+
+ // legacy
+ this.sID = this.id;
+
this.url = oOptions.url;
this.options = _mixin(oOptions);
// per-play-instance-specific options
this.instanceOptions = this.options;
@@ -1318,11 +1357,15 @@
this._iO = this.instanceOptions;
// assign property defaults
this.pan = this.options.pan;
this.volume = this.options.volume;
+
+ // whether or not this object is using HTML5
this.isHTML5 = false;
+
+ // internal HTML5 Audio() object reference
this._a = null;
/**
* SMSound() public methods
* ------------------------
@@ -1343,11 +1386,11 @@
var stuff = null, msg = [], sF, sfBracket, maxLength = 64;
for (stuff in _t.options) {
if (_t.options[stuff] !== null) {
- if (_t.options[stuff] instanceof Function) {
+ if (typeof _t.options[stuff] === 'function') {
// handle functions specially
sF = _t.options[stuff].toString();
// normalize spaces
sF = sF.replace(/\s\s+/g, ' ');
sfBracket = sF.indexOf('{');
@@ -1414,54 +1457,78 @@
// local shortcut
_iO = _t._iO;
_lastURL = _t.url;
+
+ // reset a few state properties
+
_t.loaded = false;
_t.readyState = 1;
_t.playState = 0;
+ _t.id3 = {};
// TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
if (_html5OK(_iO)) {
oS = _t._setup_html5(_iO);
if (!oS._called_load) {
- _s._wD(_h5+'load: '+_t.sID);
+ _s._wD(_h5+'load: '+_t.id);
+
_t._html5_canplay = false;
- // given explicit load call, try to get whole file.
+ // TODO: review called_load / html5_canplay logic
+
+ // if url provided directly to load(), assign it here.
+
+ if (_t._a.src !== _iO.url) {
+
+ _s._wD(_wDS('manURL') + ': ' + _iO.url);
+
+ _t._a.src = _iO.url;
+
+ // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
+
+ // reset position for new URL
+ _t.setPosition(0);
+
+ }
+
+ // given explicit load call, try to preload.
+
// early HTML5 implementation (non-standard)
_t._a.autobuffer = 'auto';
// standard
_t._a.preload = 'auto';
- oS.load();
oS._called_load = true;
if (_iO.autoPlay) {
_t.play();
}
} else {
- _s._wD(_h5+'ignoring request to load again: '+_t.sID);
+
+ _s._wD(_h5+'ignoring request to load again: '+_t.id);
+
}
} else {
try {
_t.isHTML5 = false;
_t._iO = _policyFix(_loopFix(_iO));
// re-assign local shortcut
_iO = _t._iO;
if (_fV === 8) {
- _flash._load(_t.sID, _iO.url, _iO.stream, _iO.autoPlay, (_iO.whileloading?1:0), _iO.loops||1, _iO.usePolicyFile);
+ _flash._load(_t.id, _iO.url, _iO.stream, _iO.autoPlay, (_iO.whileloading?1:0), _iO.loops||1, _iO.usePolicyFile);
} else {
- _flash._load(_t.sID, _iO.url, !!(_iO.stream), !!(_iO.autoPlay), _iO.loops||1, !!(_iO.autoLoad), _iO.usePolicyFile);
+ _flash._load(_t.id, _iO.url, !!(_iO.stream), !!(_iO.autoPlay), _iO.loops||1, !!(_iO.autoLoad), _iO.usePolicyFile);
}
} catch(e) {
_wDS('smError', 2);
_debugTS('onload', false);
_catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});
@@ -1486,24 +1553,34 @@
// Flash 9/AS3: Close stream, preventing further load
// HTML5: Most UAs will use empty URL
if (_t.readyState !== 0) {
- _s._wD('SMSound.unload(): "' + _t.sID + '"');
+ _s._wD('SMSound.unload(): "' + _t.id + '"');
if (!_t.isHTML5) {
+
if (_fV === 8) {
- _flash._unload(_t.sID, _emptyURL);
+ _flash._unload(_t.id, _emptyURL);
} else {
- _flash._unload(_t.sID);
+ _flash._unload(_t.id);
}
+
} else {
+
_stop_html5_timer();
+
if (_t._a) {
+
_t._a.pause();
- _html5Unload(_t._a);
+ _html5Unload(_t._a, _emptyURL);
+
+ // reset local URL for next load / play call, too
+ _t.url = _emptyURL;
+
}
+
}
// reset load/status flags
_resetProperties();
@@ -1517,18 +1594,18 @@
* Unloads and destroys a sound.
*/
this.destruct = function(_bFromSM) {
- _s._wD('SMSound.destruct(): "' + _t.sID + '"');
+ _s._wD('SMSound.destruct(): "' + _t.id + '"');
if (!_t.isHTML5) {
// kill sound within Flash
// Disable the onfailure handler
_t._iO.onfailure = null;
- _flash._destroySound(_t.sID);
+ _flash._destroySound(_t.id);
} else {
_stop_html5_timer();
@@ -1545,11 +1622,11 @@
}
if (!_bFromSM) {
// ensure deletion from controller
- _s.destroySound(_t.sID, true);
+ _s.destroySound(_t.id, true);
}
};
@@ -1560,17 +1637,19 @@
* @return {SMSound} The SMSound object
*/
this.play = function(oOptions, _updatePlayState) {
- var fN, allowMulti, a, onready;
+ var fN, allowMulti, a, onready, startOK = true,
+ exit = null;
// <d>
fN = 'SMSound.play(): ';
// </d>
- _updatePlayState = _updatePlayState === undefined ? true : _updatePlayState; // default to true
+ // default to true
+ _updatePlayState = (typeof _updatePlayState === 'undefined' ? true : _updatePlayState);
if (!oOptions) {
oOptions = {};
}
@@ -1595,51 +1674,61 @@
}
if (_t.playState === 1 && !_t.paused) {
allowMulti = _t._iO.multiShot;
if (!allowMulti) {
- _s._wD(fN + '"' + _t.sID + '" already playing (one-shot)', 1);
- return _t;
+ _s._wD(fN + '"' + _t.id + '" already playing (one-shot)', 1);
+ exit = _t;
} else {
- _s._wD(fN + '"' + _t.sID + '" already playing (multi-shot)', 1);
+ _s._wD(fN + '"' + _t.id + '" already playing (multi-shot)', 1);
}
}
+ if (exit !== null) {
+ return exit;
+ }
+
if (!_t.loaded) {
if (_t.readyState === 0) {
- _s._wD(fN + 'Attempting to load "' + _t.sID + '"', 1);
+ _s._wD(fN + 'Attempting to load "' + _t.id + '"', 1);
// try to get this sound playing ASAP
if (!_t.isHTML5) {
// assign directly because setAutoPlay() increments the instanceCount
_t._iO.autoPlay = true;
+ _t.load(_t._iO);
+ } else {
+ // iOS needs this when recycling sounds, loading a new URL on an existing object.
+ _t.load(_t._iO);
}
- _t.load(_t._iO);
-
} else if (_t.readyState === 2) {
- _s._wD(fN + 'Could not load "' + _t.sID + '" - exiting', 2);
- return _t;
+ _s._wD(fN + 'Could not load "' + _t.id + '" - exiting', 2);
+ exit = _t;
} else {
- _s._wD(fN + '"' + _t.sID + '" is loading - attempting to play..', 1);
+ _s._wD(fN + '"' + _t.id + '" is loading - attempting to play..', 1);
}
} else {
- _s._wD(fN + '"' + _t.sID + '"');
+ _s._wD(fN + '"' + _t.id + '"');
}
+ if (exit !== null) {
+ return exit;
+ }
+
if (!_t.isHTML5 && _fV === 9 && _t.position > 0 && _t.position === _t.duration) {
// flash 9 needs a position reset if play() is called while at the end of a sound.
- _s._wD(fN + '"' + _t.sID + '": Sound at end, resetting to position:0');
+ _s._wD(fN + '"' + _t.id + '": Sound at end, resetting to position:0');
oOptions.position = 0;
}
/**
* Streams will pause when their buffer is full if they are being loaded.
@@ -1650,11 +1739,11 @@
*/
if (_t.paused && _t.position && _t.position > 0) {
// https://gist.github.com/37b17df75cc4d7a90bf6
- _s._wD(fN + '"' + _t.sID + '" is resuming from paused state',1);
+ _s._wD(fN + '"' + _t.id + '" is resuming from paused state',1);
_t.resume();
} else {
_t._iO = _mixin(oOptions, _t._iO);
@@ -1671,46 +1760,50 @@
// HTML5 needs to at least have "canplay" fired before seeking.
if (_t.isHTML5 && !_t._html5_canplay) {
// this hasn't been loaded yet. load it first, and then do this again.
- _s._wD(fN+'Beginning load of "'+ _t.sID+'" for from/to case');
+ _s._wD(fN+'Beginning load of "'+ _t.id+'" for from/to case');
_t.load({
_oncanplay: onready
});
- return false;
+ exit = false;
} else if (!_t.isHTML5 && !_t.loaded && (!_t.readyState || _t.readyState !== 2)) {
// to be safe, preload the whole thing in Flash.
- _s._wD(fN+'Preloading "'+ _t.sID+'" for from/to case');
+ _s._wD(fN+'Preloading "'+ _t.id+'" for from/to case');
_t.load({
onload: onready
});
- return false;
+ exit = false;
}
+ if (exit !== null) {
+ return exit;
+ }
+
// otherwise, we're ready to go. re-apply local options, and continue
_t._iO = _applyFromTo();
}
- _s._wD(fN+'"'+ _t.sID+'" is starting to play');
+ _s._wD(fN+'"'+ _t.id+'" is starting to play');
if (!_t.instanceCount || _t._iO.multiShotEvents || (!_t.isHTML5 && _fV > 8 && !_t.getAutoPlay())) {
_t.instanceCount++;
}
// if first play and onposition parameters exist, apply them now
- if (_t.playState === 0 && _t._iO.onposition) {
+ if (_t._iO.onposition && _t.playState === 0) {
_attachOnPosition(_t);
}
_t.playState = 1;
_t.paused = false;
@@ -1729,17 +1822,31 @@
_t.setVolume(_t._iO.volume, true);
_t.setPan(_t._iO.pan, true);
if (!_t.isHTML5) {
- _flash._start(_t.sID, _t._iO.loops || 1, (_fV === 9?_t._iO.position:_t._iO.position / 1000));
+ startOK = _flash._start(_t.id, _t._iO.loops || 1, (_fV === 9 ? _t._iO.position : _t._iO.position / 1000), _t._iO.multiShot);
+ if (_fV === 9 && !startOK) {
+ // edge case: no sound hardware, or 32-channel flash ceiling hit.
+ // applies only to Flash 9, non-NetStream/MovieStar sounds.
+ // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
+ _s._wD(fN+ _t.id+': No sound hardware, or 32-sound ceiling hit');
+ if (_t._iO.onplayerror) {
+ _t._iO.onplayerror.apply(_t);
+ }
+
+ }
+
} else {
_start_html5_timer();
+
a = _t._setup_html5();
+
_t.setPosition(_t._iO.position);
+
a.play();
}
}
@@ -1780,11 +1887,11 @@
_t.clearOnPosition(_iO.to);
}
if (!_t.isHTML5) {
- _flash._stop(_t.sID, bAll);
+ _flash._stop(_t.id, bAll);
// hack for netStream: just unload
if (_iO.serverURL) {
_t.unload();
}
@@ -1835,20 +1942,20 @@
* @param {boolean} autoPlay state
*/
this.setAutoPlay = function(autoPlay) {
- _s._wD('sound '+_t.sID+' turned autoplay ' + (autoPlay ? 'on' : 'off'));
+ _s._wD('sound '+_t.id+' turned autoplay ' + (autoPlay ? 'on' : 'off'));
_t._iO.autoPlay = autoPlay;
if (!_t.isHTML5) {
- _flash._setAutoPlay(_t.sID, autoPlay);
+ _flash._setAutoPlay(_t.id, autoPlay);
if (autoPlay) {
// only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
if (!_t.instanceCount && _t.readyState === 1) {
_t.instanceCount++;
- _s._wD('sound '+_t.sID+' incremented instance count to '+_t.instanceCount);
+ _s._wD('sound '+_t.id+' incremented instance count to '+_t.instanceCount);
}
}
}
};
@@ -1872,11 +1979,11 @@
* @return {SMSound} The SMSound object
*/
this.setPosition = function(nMsecOffset) {
- if (nMsecOffset === undefined) {
+ if (typeof nMsecOffset === 'undefined') {
nMsecOffset = 0;
}
var original_pos,
position, position1K,
@@ -1893,11 +2000,11 @@
if (!_t.isHTML5) {
position = (_fV === 9 ? _t.position : position1K);
if (_t.readyState && _t.readyState !== 2) {
// if paused or not playing, will not resume (by playing)
- _flash._setPosition(_t.sID, position, (_t.paused || !_t.playState));
+ _flash._setPosition(_t.id, position, (_t.paused || !_t.playState), _t._iO.multiShot);
}
} else if (_t._a) {
// Set the position in the canplay handler if the sound is not ready yet
@@ -1951,12 +2058,12 @@
_s._wD('SMSound.pause()');
_t.paused = true;
if (!_t.isHTML5) {
- if (_bCallFlash || _bCallFlash === undefined) {
- _flash._pause(_t.sID);
+ if (_bCallFlash || typeof _bCallFlash === 'undefined') {
+ _flash._pause(_t.id, _t._iO.multiShot);
}
} else {
_t._setup_html5().pause();
_stop_html5_timer();
}
@@ -1999,11 +2106,11 @@
if (_iO.isMovieStar && !_iO.serverURL) {
// Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.
_t.setPosition(_t.position);
}
// flash method is toggle-based (pause/resume)
- _flash._pause(_t.sID);
+ _flash._pause(_t.id, _iO.multiShot);
} else {
_t._setup_html5().play();
_start_html5_timer();
}
@@ -2061,11 +2168,11 @@
if (typeof bInstanceOnly === 'undefined') {
bInstanceOnly = false;
}
if (!_t.isHTML5) {
- _flash._setPan(_t.sID, nPan);
+ _flash._setPan(_t.id, nPan);
} // else { no HTML5 pan? }
_t._iO.pan = nPan;
if (!bInstanceOnly) {
@@ -2100,11 +2207,11 @@
if (typeof _bInstanceOnly === 'undefined') {
_bInstanceOnly = false;
}
if (!_t.isHTML5) {
- _flash._setVolume(_t.sID, (_s.muted && !_t.muted) || _t.muted?0:nVol);
+ _flash._setVolume(_t.id, (_s.muted && !_t.muted) || _t.muted?0:nVol);
} else if (_t._a) {
// valid range: 0-1
_t._a.volume = Math.max(0, Math.min(1, nVol/100));
}
@@ -2128,11 +2235,11 @@
this.mute = function() {
_t.muted = true;
if (!_t.isHTML5) {
- _flash._setVolume(_t.sID, 0);
+ _flash._setVolume(_t.id, 0);
} else if (_t._a) {
_t._a.muted = true;
}
return _t;
@@ -2146,14 +2253,14 @@
*/
this.unmute = function() {
_t.muted = false;
- var hasIO = typeof _t._iO.volume !== 'undefined';
+ var hasIO = (typeof _t._iO.volume !== 'undefined');
if (!_t.isHTML5) {
- _flash._setVolume(_t.sID, hasIO?_t._iO.volume:_t.options.volume);
+ _flash._setVolume(_t.id, hasIO?_t._iO.volume:_t.options.volume);
} else if (_t._a) {
_t._a.muted = false;
}
return _t;
@@ -2290,11 +2397,11 @@
start, end;
end = function() {
// end has been reached.
- _s._wD(_t.sID + ': "to" time of ' + t + ' reached.');
+ _s._wD(_t.id + ': "to" time of ' + t + ' reached.');
// detach listener
_t.clearOnPosition(t, end);
// stop should clear this, too
@@ -2302,11 +2409,11 @@
};
start = function() {
- _s._wD(_t.sID + ': playing "from" ' + f);
+ _s._wD(_t.id + ': playing "from" ' + f);
// add listener for end
if (t !== null && !isNaN(t)) {
_t.onPosition(t, end);
}
@@ -2382,23 +2489,27 @@
_stopTimer(_t);
}
};
- _resetProperties = function() {
+ _resetProperties = function(retainPosition) {
- _onPositionItems = [];
- _onPositionFired = 0;
+ if (!retainPosition) {
+ _onPositionItems = [];
+ _onPositionFired = 0;
+ }
+
_onplay_called = false;
_t._hasTimer = null;
_t._a = null;
_t._html5_canplay = false;
_t.bytesLoaded = null;
_t.bytesTotal = null;
_t.duration = (_t._iO && _t._iO.duration ? _t._iO.duration : null);
_t.durationEstimate = null;
+ _t.buffered = [];
// legacy: 1D array
_t.eqData = [];
_t.eqData.left = [];
@@ -2428,10 +2539,12 @@
};
_t.playState = 0;
_t.position = null;
+ _t.id3 = {};
+
};
_resetProperties();
/**
@@ -2481,20 +2594,20 @@
_t._whileplaying(time,x,x,x,x);
}
- return isNew;
+ }/* else {
- } else {
+ // _s._wD('_onTimer: Warn for "'+_t.id+'": '+(!_t._a?'Could not find element. ':'')+(_t.playState === 0?'playState bad, 0?':'playState = '+_t.playState+', OK'));
- // _s._wD('_onTimer: Warn for "'+_t.sID+'": '+(!_t._a?'Could not find element. ':'')+(_t.playState === 0?'playState bad, 0?':'playState = '+_t.playState+', OK'));
-
return false;
- }
+ }*/
+ return isNew;
+
}
};
this._get_html5_duration = function() {
@@ -2505,65 +2618,107 @@
return result;
};
+ this._apply_loop = function(a, nLoops) {
+
+ /**
+ * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
+ * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
+ */
+
+ // <d>
+ if (!a.loop && nLoops > 1) {
+ _s._wD('Note: Native HTML5 looping is infinite.');
+ }
+ // </d>
+
+ a.loop = (nLoops > 1 ? 'loop' : '');
+
+ };
+
this._setup_html5 = function(oOptions) {
var _iO = _mixin(_t._iO, oOptions), d = decodeURI,
_a = _useGlobalHTML5Audio ? _s._global_a : _t._a,
_dURL = d(_iO.url),
- _oldIO = (_a && _a._t ? _a._t.instanceOptions : null);
+ _oldIO = (_a && _a._t ? _a._t.instanceOptions : null),
+ result;
if (_a) {
if (_a._t) {
if (!_useGlobalHTML5Audio && _dURL === d(_lastURL)) {
+
// same url, ignore request
- return _a;
+ result = _a;
+
} else if (_useGlobalHTML5Audio && _oldIO.url === _iO.url && (!_lastURL || (_lastURL === _oldIO.url))) {
+
// iOS-type reuse case
- return _a;
+ result = _a;
+
}
+ if (result) {
+
+ _t._apply_loop(_a, _iO.loops);
+ return result;
+
+ }
+
}
- _s._wD('setting new URL on existing object: ' + _dURL + (_lastURL ? ', old URL: ' + _lastURL : ''));
+ _s._wD('setting URL on existing object: ' + _dURL + (_lastURL ? ', old URL: ' + _lastURL : ''));
/**
* "First things first, I, Poppa.." (reset the previous state of the old sound, if playing)
* Fixes case with devices that can only play one sound at a time
* Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state
*/
if (_useGlobalHTML5Audio && _a._t && _a._t.playState && _iO.url !== _oldIO.url) {
+
_a._t.stop();
+
}
- // new URL, so reset load/playstate and so on
- _resetProperties();
+ // reset load/playstate, onPosition etc. if the URL is new.
+ // somewhat-tricky object re-use vs. new SMSound object, old vs. new URL comparisons
+ _resetProperties((_oldIO && _oldIO.url ? _iO.url === _oldIO.url : (_lastURL ? _lastURL === _iO.url : false)));
_a.src = _iO.url;
_t.url = _iO.url;
_lastURL = _iO.url;
_a._called_load = false;
} else {
- _s._wD('creating HTML5 Audio() element with URL: '+_dURL);
- _a = new Audio(_iO.url);
+ _wDS('h5a');
- _a._called_load = false;
+ if (_iO.autoLoad || _iO.autoPlay) {
- // android (seen in 2.3/Honeycomb) sometimes fails first .load() -> .play(), results in playback failure and ended() events?
- if (_is_android) {
- _a._called_load = true;
+ _t._a = new Audio(_iO.url);
+
+ } else {
+
+ // null for stupid Opera 9.64 case
+ _t._a = (_isOpera ? new Audio(null) : new Audio());
+
}
+ // assign local reference
+ _a = _t._a;
+
+ _a._called_load = false;
+
if (_useGlobalHTML5Audio) {
+
_s._global_a = _a;
+
}
}
_t.isHTML5 = true;
@@ -2573,29 +2728,27 @@
// store a ref on the audio
_a._t = _t;
_add_html5_events();
- _a.loop = (_iO.loops>1?'loop':'');
+ _t._apply_loop(_a, _iO.loops);
+
if (_iO.autoLoad || _iO.autoPlay) {
_t.load();
} else {
// early HTML5 implementation (non-standard)
_a.autobuffer = false;
- // standard
- _a.preload = 'none';
+ // standard ('none' is also an option.)
+ _a.preload = 'auto';
}
- // boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
- _a.loop = (_iO.loops > 1 ? 'loop' : '');
-
return _a;
};
_add_html5_events = function() {
@@ -2608,11 +2761,10 @@
function add(oEvt, oFn, bCapture) {
return _t._a ? _t._a.addEventListener(oEvt, oFn, bCapture||false) : null;
}
- _s._wD(_h5+'adding event listeners: '+_t.sID);
_t._a._added_events = true;
for (f in _html5_events) {
if (_html5_events.hasOwnProperty(f)) {
add(f, _html5_events[f]);
@@ -2631,11 +2783,11 @@
function remove(oEvt, oFn, bCapture) {
return (_t._a ? _t._a.removeEventListener(oEvt, oFn, bCapture||false) : null);
}
- _s._wD(_h5+'removing event listeners: '+_t.sID);
+ _s._wD(_h5+'removing event listeners: '+_t.id);
_t._a._added_events = false;
for (f in _html5_events) {
if (_html5_events.hasOwnProperty(f)) {
remove(f, _html5_events[f]);
@@ -2650,15 +2802,17 @@
*/
this._onload = function(nSuccess) {
- var fN, loadOK = !!(nSuccess);
+ var fN,
+ // check for duration to prevent false positives from flash 8 when loading from cache.
+ loadOK = (!!(nSuccess) || (!_t.isHTML5 && _fV === 8 && _t.duration));
// <d>
fN = 'SMSound._onload(): ';
- _s._wD(fN + '"' + _t.sID + '"' + (loadOK?' loaded.':' failed to load? - ' + _t.url), (loadOK?1:2));
+ _s._wD(fN + '"' + _t.id + '"' + (loadOK?' loaded.':' failed to load? - ' + _t.url), (loadOK?1:2));
if (!loadOK && !_t.isHTML5) {
if (_s.sandbox.noRemote === true) {
_s._wD(fN + _str('noNet'), 1);
}
if (_s.sandbox.noLocal === true) {
@@ -2722,11 +2876,11 @@
*/
this._onfailure = function(msg, level, code) {
_t.failures++;
- _s._wD('SMSound._onfailure(): "'+_t.sID+'" count '+_t.failures);
+ _s._wD('SMSound._onfailure(): "'+_t.id+'" count '+_t.failures);
if (_t._iO.onfailure && _t.failures === 1) {
_t._iO.onfailure(_t, msg, level, code);
} else {
_s._wD('SMSound._onfailure(): ignoring');
@@ -2758,16 +2912,21 @@
_t.instanceCount = 0;
_t.instanceOptions = {};
_t._iO = {};
_stop_html5_timer();
+ // reset position, too
+ if (_t.isHTML5) {
+ _t.position = 0;
+ }
+
}
if (!_t.instanceCount || _t._iO.multiShotEvents) {
// fire onfinish for last, or every instance
if (_io_onfinish) {
- _s._wD('SMSound._onfinish(): "' + _t.sID + '"');
+ _s._wD('SMSound._onfinish(): "' + _t.id + '"');
_io_onfinish.apply(_t);
}
}
}
@@ -2784,34 +2943,39 @@
_t.bufferLength = nBufferLength;
if (!_iO.isMovieStar) {
if (_iO.duration) {
- // use options, if specified and larger
+ // use duration from options, if specified and larger
_t.durationEstimate = (_t.duration > _iO.duration) ? _t.duration : _iO.duration;
} else {
_t.durationEstimate = parseInt((_t.bytesTotal / _t.bytesLoaded) * _t.duration, 10);
-
}
- if (_t.durationEstimate === undefined) {
+ if (typeof _t.durationEstimate === 'undefined') {
_t.durationEstimate = _t.duration;
}
- if (_t.readyState !== 3 && _iO.whileloading) {
- _iO.whileloading.apply(_t);
- }
-
} else {
_t.durationEstimate = _t.duration;
- if (_t.readyState !== 3 && _iO.whileloading) {
- _iO.whileloading.apply(_t);
- }
}
+ // for flash, reflect sequential-load-style buffering
+ if (!_t.isHTML5) {
+ _t.buffered = [{
+ 'start': 0,
+ 'end': _t.duration
+ }];
+ }
+
+ // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
+ if ((_t.readyState !== 3 || _t.isHTML5) && _iO.whileloading) {
+ _iO.whileloading.apply(_t);
+ }
+
};
this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {
var _iO = _t._iO,
@@ -2820,11 +2984,13 @@
if (isNaN(nPosition) || nPosition === null) {
// flash safety net
return false;
}
- _t.position = nPosition;
+ // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.
+ _t.position = Math.max(0, nPosition);
+
_t._processOnPosition();
if (!_t.isHTML5 && _fV > 8) {
if (_iO.usePeakData && typeof oPeakData !== 'undefined' && oPeakData) {
@@ -2870,21 +3036,39 @@
return true;
};
+ this._oncaptiondata = function(oData) {
+
+ /**
+ * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
+ *
+ * @param {object} oData
+ */
+
+ _s._wD('SMSound._oncaptiondata(): "' + this.id + '" caption data received.');
+
+ _t.captiondata = oData;
+
+ if (_t._iO.oncaptiondata) {
+ _t._iO.oncaptiondata.apply(_t);
+ }
+
+ };
+
this._onmetadata = function(oMDProps, oMDData) {
/**
* internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
* RTMP may include song title, MovieStar content may include encoding info
*
* @param {array} oMDProps (names)
* @param {array} oMDData (values)
*/
- _s._wD('SMSound._onmetadata(): "' + this.sID + '" metadata received.');
+ _s._wD('SMSound._onmetadata(): "' + this.id + '" metadata received.');
var oData = {}, i, j;
for (i = 0, j = oMDProps.length; i < j; i++) {
oData[oMDProps[i]] = oMDData[i];
@@ -2905,11 +3089,11 @@
*
* @param {array} oID3Props (names)
* @param {array} oID3Data (values)
*/
- _s._wD('SMSound._onid3(): "' + this.sID + '" ID3 data received.');
+ _s._wD('SMSound._onid3(): "' + this.id + '" ID3 data received.');
var oData = [], i, j;
for (i = 0, j = oID3Props.length; i < j; i++) {
oData[oID3Props[i]] = oID3Data[i];
@@ -2925,18 +3109,18 @@
// flash/RTMP-only
this._onconnect = function(bSuccess) {
bSuccess = (bSuccess === 1);
- _s._wD('SMSound._onconnect(): "'+_t.sID+'"'+(bSuccess?' connected.':' failed to connect? - '+_t.url), (bSuccess?1:2));
+ _s._wD('SMSound._onconnect(): "'+_t.id+'"'+(bSuccess?' connected.':' failed to connect? - '+_t.url), (bSuccess?1:2));
_t.connected = bSuccess;
if (bSuccess) {
_t.failures = 0;
- if (_idCheck(_t.sID)) {
+ if (_idCheck(_t.id)) {
if (_t.getAutoPlay()) {
// only update the play state if auto playing
_t.play(undefined, _t.getAutoPlay());
} else if (_t._iO.autoLoad) {
_t.load();
@@ -2984,29 +3168,187 @@
};
_mixin = function(oMain, oAdd) {
// non-destructive merge
- var o1 = {}, i, o2, o;
+ var o1 = (oMain || {}), o2, o;
- // clone c1
- for (i in oMain) {
- if (oMain.hasOwnProperty(i)) {
- o1[i] = oMain[i];
- }
- }
+ // if unspecified, o2 is the default options object
+ o2 = (typeof oAdd === 'undefined' ? _s.defaultOptions : oAdd);
- o2 = (typeof oAdd === 'undefined'?_s.defaultOptions:oAdd);
for (o in o2) {
+
if (o2.hasOwnProperty(o) && typeof o1[o] === 'undefined') {
- o1[o] = o2[o];
+
+ if (typeof o2[o] !== 'object' || o2[o] === null) {
+
+ // assign directly
+ o1[o] = o2[o];
+
+ } else {
+
+ // recurse through o2
+ o1[o] = _mixin(o1[o], o2[o]);
+
+ }
+
}
+
}
+
return o1;
};
+ // additional soundManager properties that soundManager.setup() will accept
+
+ _extraOptions = {
+ 'onready': 1,
+ 'ontimeout': 1,
+ 'defaultOptions': 1,
+ 'flash9Options': 1,
+ 'movieStarOptions': 1
+ };
+
+ _assign = function(o, oParent) {
+
+ /**
+ * recursive assignment of properties, soundManager.setup() helper
+ * allows property assignment based on whitelist
+ */
+
+ var i,
+ result = true,
+ hasParent = (typeof oParent !== 'undefined'),
+ setupOptions = _s.setupOptions,
+ extraOptions = _extraOptions;
+
+ // <d>
+
+ // if soundManager.setup() called, show accepted parameters.
+
+ if (typeof o === 'undefined') {
+
+ result = [];
+
+ for (i in setupOptions) {
+
+ if (setupOptions.hasOwnProperty(i)) {
+ result.push(i);
+ }
+
+ }
+
+ for (i in extraOptions) {
+
+ if (extraOptions.hasOwnProperty(i)) {
+
+ if (typeof _s[i] === 'object') {
+
+ result.push(i+': {...}');
+
+ } else if (_s[i] instanceof Function) {
+
+ result.push(i+': function() {...}');
+
+ } else {
+
+ result.push(i);
+
+ }
+
+ }
+
+ }
+
+ _s._wD(_str('setup', result.join(', ')));
+
+ return false;
+
+ }
+
+ // </d>
+
+ for (i in o) {
+
+ if (o.hasOwnProperty(i)) {
+
+ // if not an {object} we want to recurse through...
+
+ if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array) {
+
+ // check "allowed" options
+
+ if (hasParent && typeof extraOptions[oParent] !== 'undefined') {
+
+ // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
+ _s[oParent][i] = o[i];
+
+ } else if (typeof setupOptions[i] !== 'undefined') {
+
+ // special case: assign to setupOptions object, which soundManager property references
+ _s.setupOptions[i] = o[i];
+
+ // assign directly to soundManager, too
+ _s[i] = o[i];
+
+ } else if (typeof extraOptions[i] === 'undefined') {
+
+ // invalid or disallowed parameter. complain.
+ _complain(_str((typeof _s[i] === 'undefined' ? 'setupUndef' : 'setupError'), i), 2);
+
+ result = false;
+
+ } else {
+
+ /**
+ * valid extraOptions parameter.
+ * is it a method, like onready/ontimeout? call it.
+ * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
+ */
+
+ if (_s[i] instanceof Function) {
+
+ _s[i].apply(_s, (o[i] instanceof Array? o[i] : [o[i]]));
+
+ } else {
+
+ // good old-fashioned direct assignment
+ _s[i] = o[i];
+
+ }
+
+ }
+
+ } else {
+
+ // recursion case, eg., { defaultOptions: { ... } }
+
+ if (typeof extraOptions[i] === 'undefined') {
+
+ // invalid or disallowed parameter. complain.
+ _complain(_str((typeof _s[i] === 'undefined' ? 'setupUndef' : 'setupError'), i), 2);
+
+ result = false;
+
+ } else {
+
+ // recurse through object
+ return _assign(o[i], i);
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return result;
+
+ };
+
_event = (function() {
var old = (_win.attachEvent),
evt = {
add: (old?'attachEvent':'addEventListener'),
@@ -3062,10 +3404,17 @@
'remove': remove
};
}());
+ function _preferFlashCheck(kind) {
+
+ // whether flash should play a given type
+ return (_s.preferFlash && _hasFlash && !_s.ignoreFlash && (typeof _s.flash[kind] !== 'undefined' && _s.flash[kind]));
+
+ }
+
/**
* Internal HTML5 event handling
* -----------------------------
*/
@@ -3073,36 +3422,39 @@
// wrap html5 event handlers so we don't call them on destroyed sounds
return function(e) {
- var t = this._t;
+ var t = this._t,
+ result;
if (!t || !t._a) {
// <d>
- if (t && t.sID) {
- _s._wD(_h5+'ignoring '+e.type+': '+t.sID);
+ if (t && t.id) {
+ _s._wD(_h5+'ignoring '+e.type+': '+t.id);
} else {
_s._wD(_h5+'ignoring '+e.type);
}
// </d>
- return null;
+ result = null;
} else {
- return oFn.call(this, e);
+ result = oFn.call(this, e);
}
+ return result;
+
};
}
_html5_events = {
// HTML5 event-name-to-handler map
abort: _html5_event(function() {
- _s._wD(_h5+'abort: '+this._t.sID);
+ _s._wD(_h5+'abort: '+this._t.id);
}),
// enough has loaded to play
@@ -3115,58 +3467,59 @@
// this event has already fired. ignore.
return true;
}
t._html5_canplay = true;
- _s._wD(_h5+'canplay: '+t.sID+', '+t.url);
+ _s._wD(_h5+'canplay: '+t.id+', '+t.url);
t._onbufferchange(0);
- position1K = (!isNaN(t.position)?t.position/1000:null);
+ // position according to instance options
+ position1K = (typeof t._iO.position !== 'undefined' && !isNaN(t._iO.position)?t._iO.position/1000:null);
+
// set the position if position was set before the sound loaded
if (t.position && this.currentTime !== position1K) {
_s._wD(_h5+'canplay: setting position to '+position1K);
try {
this.currentTime = position1K;
} catch(ee) {
- _s._wD(_h5+'setting position failed: '+ee.message, 2);
+ _s._wD(_h5+'setting position of ' + position1K + ' failed: '+ee.message, 2);
}
}
// hack for HTML5 from/to case
if (t._iO._oncanplay) {
t._iO._oncanplay();
}
}),
- load: _html5_event(function() {
+ canplaythrough: _html5_event(function() {
var t = this._t;
if (!t.loaded) {
t._onbufferchange(0);
- // should be 1, and the same
- t._whileloading(t.bytesTotal, t.bytesTotal, t._get_html5_duration());
+ t._whileloading(t.bytesLoaded, t.bytesTotal, t._get_html5_duration());
t._onload(true);
}
}),
// TODO: Reserved for potential use
/*
emptied: _html5_event(function() {
- _s._wD(_h5+'emptied: '+this._t.sID);
+ _s._wD(_h5+'emptied: '+this._t.id);
}),
*/
ended: _html5_event(function() {
var t = this._t;
- _s._wD(_h5+'ended: '+t.sID);
+ _s._wD(_h5+'ended: '+t.id);
t._onfinish();
}),
error: _html5_event(function() {
@@ -3177,80 +3530,83 @@
}),
loadeddata: _html5_event(function() {
- var t = this._t,
- // at least 1 byte, so math works
- bytesTotal = t.bytesTotal || 1;
+ var t = this._t;
- _s._wD(_h5+'loadeddata: '+this._t.sID);
+ _s._wD(_h5+'loadeddata: '+this._t.id);
// safari seems to nicely report progress events, eventually totalling 100%
if (!t._loaded && !_isSafari) {
t.duration = t._get_html5_duration();
- // fire whileloading() with 100% values
- t._whileloading(bytesTotal, bytesTotal, t._get_html5_duration());
- t._onload(true);
}
}),
loadedmetadata: _html5_event(function() {
- _s._wD(_h5+'loadedmetadata: '+this._t.sID);
+ _s._wD(_h5+'loadedmetadata: '+this._t.id);
}),
loadstart: _html5_event(function() {
- _s._wD(_h5+'loadstart: '+this._t.sID);
+ _s._wD(_h5+'loadstart: '+this._t.id);
// assume buffering at first
this._t._onbufferchange(1);
}),
play: _html5_event(function() {
- _s._wD(_h5+'play: '+this._t.sID+', '+this._t.url);
+ _s._wD(_h5+'play: '+this._t.id+', '+this._t.url);
// once play starts, no buffering
this._t._onbufferchange(0);
}),
playing: _html5_event(function() {
- _s._wD(_h5+'playing: '+this._t.sID);
+ _s._wD(_h5+'playing: '+this._t.id);
// once play starts, no buffering
this._t._onbufferchange(0);
}),
progress: _html5_event(function(e) {
+ // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
+
var t = this._t,
i, j, str, buffered = 0,
isProgress = (e.type === 'progress'),
ranges = e.target.buffered,
// firefox 3.6 implements e.loaded/total (bytes)
loaded = (e.loaded||0),
total = (e.total||1);
- if (t.loaded) {
- return false;
- }
+ // reset the "buffered" (loaded byte ranges) array
+ t.buffered = [];
if (ranges && ranges.length) {
// if loaded is 0, try TimeRanges implementation as % of load
// https://developer.mozilla.org/en/DOM/TimeRanges
- for (i=ranges.length-1; i >= 0; i--) {
- buffered = (ranges.end(i) - ranges.start(i));
+ // re-build "buffered" array
+ for (i=0, j=ranges.length; i<j; i++) {
+ t.buffered.push({
+ 'start': ranges.start(i),
+ 'end': ranges.end(i)
+ });
}
+ // use the last value locally
+ buffered = (ranges.end(0) - ranges.start(0));
+
// linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
loaded = buffered/e.target.duration;
// <d>
if (isProgress && ranges.length > 1) {
@@ -3261,50 +3617,51 @@
}
_s._wD(_h5+'progress: timeRanges: '+str.join(', '));
}
if (isProgress && !isNaN(loaded)) {
- _s._wD(_h5+'progress: '+t.sID+': ' + Math.floor(loaded*100)+'% loaded');
+ _s._wD(_h5+'progress: '+t.id+': ' + Math.floor(loaded*100)+'% loaded');
}
// </d>
}
if (!isNaN(loaded)) {
// if progress, likely not buffering
t._onbufferchange(0);
+ // TODO: prevent calls with duplicate values.
t._whileloading(loaded, total, t._get_html5_duration());
if (loaded && total && loaded === total) {
// in case "onload" doesn't fire (eg. gecko 1.9.2)
- _html5_events.load.call(this, e);
+ _html5_events.canplaythrough.call(this, e);
}
}
}),
ratechange: _html5_event(function() {
- _s._wD(_h5+'ratechange: '+this._t.sID);
+ _s._wD(_h5+'ratechange: '+this._t.id);
}),
suspend: _html5_event(function(e) {
// download paused/stopped, may have finished (eg. onload)
var t = this._t;
- _s._wD(_h5+'suspend: '+t.sID);
+ _s._wD(_h5+'suspend: '+t.id);
_html5_events.progress.call(this, e);
t._onsuspend();
}),
stalled: _html5_event(function() {
- _s._wD(_h5+'stalled: '+this._t.sID);
+ _s._wD(_h5+'stalled: '+this._t.id);
}),
timeupdate: _html5_event(function() {
@@ -3315,38 +3672,54 @@
waiting: _html5_event(function() {
var t = this._t;
// see also: seeking
- _s._wD(_h5+'waiting: '+t.sID);
+ _s._wD(_h5+'waiting: '+t.id);
// playback faster than download rate, etc.
t._onbufferchange(1);
})
};
_html5OK = function(iO) {
- // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
- return (!iO.serverURL && (iO.type?_html5CanPlay({type:iO.type}):_html5CanPlay({url:iO.url})||_s.html5Only));
+ // playability test based on URL or MIME type
+ var result;
+
+ if (iO.serverURL || (iO.type && _preferFlashCheck(iO.type))) {
+
+ // RTMP, or preferring flash
+ result = false;
+
+ } else {
+
+ // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
+ result = ((iO.type ? _html5CanPlay({type:iO.type}) : _html5CanPlay({url:iO.url}) || _s.html5Only));
+
+ }
+
+ return result;
+
};
- _html5Unload = function(oAudio) {
+ _html5Unload = function(oAudio, url) {
/**
* Internal method: Unload media, and cancel any current/pending network requests.
* Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
* https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
+ * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
* Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
*/
if (oAudio) {
- // Firefox likes '' for unload, most other UAs don't and fail to unload.
- oAudio.src = (_is_firefox ? '' : _emptyURL);
+ // Firefox likes '' for unload (used to work?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
+ oAudio.src = url;
}
};
_html5CanPlay = function(o) {
@@ -3369,21 +3742,14 @@
result,
offset,
fileExt,
item;
- function preferFlashCheck(kind) {
-
- // whether flash should play a given type
- return (_s.preferFlash && _hasFlash && !_s.ignoreFlash && (typeof _s.flash[kind] !== 'undefined' && _s.flash[kind]));
-
- }
-
// account for known cases like audio/mp3
if (mime && typeof _s.html5[mime] !== 'undefined') {
- return (_s.html5[mime] && !preferFlashCheck(mime));
+ return (_s.html5[mime] && !_preferFlashCheck(mime));
}
if (!_html5Ext) {
_html5Ext = [];
for (item in aF) {
@@ -3400,11 +3766,11 @@
// TODO: Strip URL queries, etc.
fileExt = (url ? url.toLowerCase().match(_html5Ext) : null);
if (!fileExt || !fileExt.length) {
if (!mime) {
- return false;
+ result = false;
} else {
// audio/mp3 -> mp3, result should be known
offset = mime.indexOf(';');
// strip "audio/X; codecs.."
fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6);
@@ -3414,87 +3780,108 @@
fileExt = fileExt[1];
}
if (fileExt && typeof _s.html5[fileExt] !== 'undefined') {
// result known
- return (_s.html5[fileExt] && !preferFlashCheck(fileExt));
+ result = (_s.html5[fileExt] && !_preferFlashCheck(fileExt));
} else {
mime = 'audio/'+fileExt;
result = _s.html5.canPlayType({type:mime});
_s.html5[fileExt] = result;
// _s._wD('canPlayType, found result: '+result);
- return (result && _s.html5[mime] && !preferFlashCheck(mime));
+ result = (result && _s.html5[mime] && !_preferFlashCheck(mime));
}
+ return result;
+
};
_testHTML5 = function() {
if (!_s.useHTML5Audio || typeof Audio === 'undefined') {
return false;
}
// double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/
var a = (typeof Audio !== 'undefined' ? (_isOpera ? new Audio(null) : new Audio()) : null),
- item, support = {}, aF, i;
+ item, lookup, support = {}, aF, i;
function _cp(m) {
- var canPlay, i, j, isOK = false;
+ var canPlay, i, j,
+ result = false,
+ isOK = false;
if (!a || typeof a.canPlayType !== 'function') {
- return false;
+ return result;
}
if (m instanceof Array) {
// iterate through all mime types, return any successes
for (i=0, j=m.length; i<j && !isOK; i++) {
if (_s.html5[m[i]] || a.canPlayType(m[i]).match(_s.html5Test)) {
isOK = true;
_s.html5[m[i]] = true;
-
- // if flash can play and preferred, also mark it for use.
- _s.flash[m[i]] = !!(_s.preferFlash && _hasFlash && m[i].match(_flashMIME));
-
+ // note flash support, too
+ _s.flash[m[i]] = !!(m[i].match(_flashMIME));
}
}
- return isOK;
+ result = isOK;
} else {
canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);
- return !!(canPlay && (canPlay.match(_s.html5Test)));
+ result = !!(canPlay && (canPlay.match(_s.html5Test)));
}
+ return result;
+
}
// test all registered formats + codecs
aF = _s.audioFormats;
for (item in aF) {
+
if (aF.hasOwnProperty(item)) {
+
+ lookup = 'audio/' + item;
+
support[item] = _cp(aF[item].type);
// write back generic type too, eg. audio/mp3
- support['audio/'+item] = support[item];
+ support[lookup] = support[item];
// assign flash
- if (_s.preferFlash && !_s.ignoreFlash && item.match(_flashMIME)) {
+ if (item.match(_flashMIME)) {
+
_s.flash[item] = true;
+ _s.flash[lookup] = true;
+
} else {
+
_s.flash[item] = false;
+ _s.flash[lookup] = false;
+
}
// assign result to related formats, too
+
if (aF[item] && aF[item].related) {
+
for (i=aF[item].related.length-1; i >= 0; i--) {
+
// eg. audio/m4a
support['audio/'+aF[item].related[i]] = support[item];
_s.html5[aF[item].related[i]] = support[item];
_s.flash[aF[item].related[i]] = support[item];
+
}
+
}
+
}
+
}
support.canPlayType = (a?_cp:null);
_s.html5 = _mixin(_s.html5, support);
@@ -3503,51 +3890,56 @@
};
_strings = {
// <d>
- notReady: 'Not loaded yet - wait for soundManager.onload()/onready()',
+ notReady: 'Not loaded yet - wait for soundManager.onready()',
notOK: 'Audio support is not available.',
domError: _smc + 'createMovie(): appendChild/innerHTML call failed. DOM not ready or other error.',
spcWmode: _smc + 'createMovie(): Removing wmode, preventing known SWF loading issue(s)',
swf404: _sm + ': Verify that %s is a valid path.',
tryDebug: 'Try ' + _sm + '.debugFlash = true for more security details (output goes to SWF.)',
checkSWF: 'See SWF output for more debug info.',
localFail: _sm + ': Non-HTTP page (' + _doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/',
- waitFocus: _sm + ': Special case: Waiting for focus-related event..',
+ waitFocus: _sm + ': Special case: Waiting for SWF to load with window focus...',
waitImpatient: _sm + ': Getting impatient, still waiting for Flash%s...',
waitForever: _sm + ': Waiting indefinitely for Flash (will recover if unblocked)...',
+ waitSWF: _sm + ': Retrying, waiting for 100% SWF load...',
needFunction: _sm + ': Function object expected for %s',
badID: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',
currentObj: '--- ' + _sm + '._debug(): Current sound objects ---',
- waitEI: _smc + 'initMovie(): Waiting for ExternalInterface call from Flash..',
+ waitEI: _smc + 'initMovie(): Waiting for ExternalInterface call from Flash...',
waitOnload: _sm + ': Waiting for window.onload()',
docLoaded: _sm + ': Document already loaded',
onload: _smc + 'initComplete(): calling soundManager.onload()',
onloadOK: _sm + '.onload() complete',
init: _smc + 'init()',
didInit: _smc + 'init(): Already called?',
- flashJS: _sm + ': Attempting to call Flash from JS..',
+ flashJS: _sm + ': Attempting JS to Flash call...',
secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',
badRemove: 'Warning: Failed to remove flash movie.',
- noPeak: 'Warning: peakData features unsupported for movieStar formats',
shutdown: _sm + '.disable(): Shutting down',
queue: _sm + ': Queueing %s handler',
smFail: _sm + ': Failed to initialise.',
smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
- fbTimeout: 'No flash response, applying .'+_swfCSS.swfTimedout+' CSS..',
+ fbTimeout: 'No flash response, applying .'+_swfCSS.swfTimedout+' CSS...',
fbLoaded: 'Flash loaded',
fbHandler: _smc+'flashBlockHandler()',
manURL: 'SMSound.load(): Using manually-assigned URL',
onURL: _sm + '.load(): current URL already assigned.',
badFV: _sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',
as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',
noNSLoop: 'Note: Looping not implemented for MovieStar formats',
needfl9: 'Note: Switching to flash 9, required for MP4 formats.',
mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
mfOn: 'mobileFlash::enabling on-screen flash repositioning',
- policy: 'Enabling usePolicyFile for data access'
+ policy: 'Enabling usePolicyFile for data access',
+ setup: _sm + '.setup(): allowed parameters: %s',
+ setupError: _sm + '.setup(): "%s" cannot be assigned with this method.',
+ setupUndef: _sm + '.setup(): Could not find option "%s"',
+ setupLate: _sm + '.setup(): url + flashVersion changes will not take effect until reboot().',
+ h5a: 'creating HTML5 Audio() object'
// </d>
};
_str = function() {
@@ -3802,15 +4194,11 @@
_idCheck = this.getSoundById;
// <d>
_wDS = function(o, errorLevel) {
- if (!o) {
- return '';
- } else {
- return _s._wD(_str(o), errorLevel);
- }
+ return (!o ? '' : _s._wD(_str(o), errorLevel));
};
// last-resort debugging option
@@ -3936,24 +4324,24 @@
};
_processOnEvents = function(oOptions) {
- // assume onready, if unspecified
+ // if unspecified, assume OK/error
if (!oOptions) {
oOptions = {
- type: 'onready'
+ type: (_s.ok() ? 'onready' : 'ontimeout')
};
}
if (!_didInit && oOptions && !oOptions.ignoreInit) {
// not ready yet.
return false;
}
- if (oOptions.type === 'ontimeout' && _s.ok()) {
+ if (oOptions.type === 'ontimeout' && (_s.ok() || (_disabled && !oOptions.ignoreInit))) {
// invalid case
return false;
}
var status = {
@@ -4006,11 +4394,11 @@
_processOnEvents();
// call user-defined "onload", scoped to window
- if (_s.onload instanceof Function) {
+ if (typeof _s.onload === 'function') {
_wDS('onload', 1);
_s.onload.apply(_win);
_wDS('onloadOK', 1);
}
@@ -4024,11 +4412,11 @@
_detectFlash = function() {
// hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt
- if (_hasFlash !== undefined) {
+ if (typeof _hasFlash !== 'undefined') {
// this work has already been done.
return _hasFlash;
}
var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = _win.ActiveXObject;
@@ -4054,13 +4442,15 @@
};
_featureCheck = function() {
- var needsFlash, item,
-
- // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (iPad) + iOS4 works.
+ var needsFlash,
+ item,
+ result = true,
+ formats = _s.audioFormats,
+ // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
isSpecial = (_is_iDevice && !!(_ua.match(/os (1|2|3_0|3_1)/i)));
if (isSpecial) {
// has Audio(), but is broken; let it load links directly.
@@ -4071,46 +4461,47 @@
if (_s.oMC) {
_s.oMC.style.display = 'none';
}
- return false;
+ result = false;
- }
+ } else {
- if (_s.useHTML5Audio) {
+ if (_s.useHTML5Audio) {
- if (!_s.html5 || !_s.html5.canPlayType) {
- _s._wD('SoundManager: No HTML5 Audio() support detected.');
- _s.hasHTML5 = false;
- return true;
- } else {
- _s.hasHTML5 = true;
- }
- if (_isBadSafari) {
- _s._wD(_smc+'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - '+(!_hasFlash?' would use flash fallback for MP3/MP4, but none detected.':'will use flash fallback for MP3/MP4, if available'),1);
- if (_detectFlash()) {
- return true;
+ if (!_s.html5 || !_s.html5.canPlayType) {
+ _s._wD('SoundManager: No HTML5 Audio() support detected.');
+ _s.hasHTML5 = false;
+ } else {
+ _s.hasHTML5 = true;
}
- }
- } else {
- // flash needed (or, HTML5 needs enabling.)
- return true;
+ // <d>
+ if (_isBadSafari) {
+ _s._wD(_smc+'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - '+(!_hasFlash?' would use flash fallback for MP3/MP4, but none detected.':'will use flash fallback for MP3/MP4, if available'),1);
+ }
+ // </d>
+ }
+
}
- for (item in _s.audioFormats) {
- if (_s.audioFormats.hasOwnProperty(item)) {
- if ((_s.audioFormats[item].required && !_s.html5.canPlayType(_s.audioFormats[item].type)) || _s.flash[item] || _s.flash[_s.audioFormats[item].type]) {
- // flash may be required, or preferred for this format
- needsFlash = true;
+ if (_s.useHTML5Audio && _s.hasHTML5) {
+
+ for (item in formats) {
+ if (formats.hasOwnProperty(item)) {
+ if ((formats[item].required && !_s.html5.canPlayType(formats[item].type)) || (_s.preferFlash && (_s.flash[item] || _s.flash[formats[item].type]))) {
+ // flash may be required, or preferred for this format
+ needsFlash = true;
+ }
}
}
+
}
- // sanity check..
+ // sanity check...
if (_s.ignoreFlash) {
needsFlash = false;
}
_s.html5Only = (_s.hasHTML5 && _s.useHTML5Audio && !needsFlash);
@@ -4124,46 +4515,48 @@
/**
* Internal: Finds and returns the first playable URL (or failing that, the first URL.)
* @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
*/
- var i, j, result = 0;
+ var i, j, urlResult = 0, result;
if (url instanceof Array) {
// find the first good one
for (i=0, j=url.length; i<j; i++) {
if (url[i] instanceof Object) {
// MIME check
if (_s.canPlayMIME(url[i].type)) {
- result = i;
+ urlResult = i;
break;
}
} else if (_s.canPlayURL(url[i])) {
// URL string check
- result = i;
+ urlResult = i;
break;
}
}
// normalize to string
- if (url[result].url) {
- url[result] = url[result].url;
+ if (url[urlResult].url) {
+ url[urlResult] = url[urlResult].url;
}
- return url[result];
+ result = url[urlResult];
} else {
// single URL case
- return url;
+ result = url;
}
+ return result;
+
};
_startTimer = function(oSound) {
@@ -4173,15 +4566,15 @@
if (!oSound._hasTimer) {
oSound._hasTimer = true;
- if (!_likesHTML5 && _s.html5PollingInterval) {
+ if (!_mobileHTML5 && _s.html5PollingInterval) {
if (_h5IntervalTimer === null && _h5TimerCount === 0) {
- _h5IntervalTimer = window.setInterval(_timerExecute, _s.html5PollingInterval);
+ _h5IntervalTimer = _win.setInterval(_timerExecute, _s.html5PollingInterval);
}
_h5TimerCount++;
@@ -4199,11 +4592,11 @@
if (oSound._hasTimer) {
oSound._hasTimer = false;
- if (!_likesHTML5 && _s.html5PollingInterval) {
+ if (!_mobileHTML5 && _s.html5PollingInterval) {
// interval will stop itself at next execution.
_h5TimerCount--;
@@ -4223,11 +4616,11 @@
if (_h5IntervalTimer !== null && !_h5TimerCount) {
// no active timers, stop polling interval.
- window.clearInterval(_h5IntervalTimer);
+ _win.clearInterval(_h5IntervalTimer);
_h5IntervalTimer = null;
return false;
@@ -4249,11 +4642,11 @@
_catchError = function(options) {
options = (typeof options !== 'undefined' ? options : {});
- if (_s.onerror instanceof Function) {
+ if (typeof _s.onerror === 'function') {
_s.onerror.apply(_win, [{type:(typeof options.type !== 'undefined' ? options.type : null)}]);
}
if (typeof options.fatal !== 'undefined' && options.fatal) {
_s.disable();
@@ -4361,16 +4754,12 @@
return false;
}
// </d>
- if (_isIE) {
- // IE needs a timeout OR delay until window.onload - may need TODO: investigating
- setTimeout(_init, 100);
- } else {
- _init();
- }
+ // slight delay before init
+ setTimeout(_init, _isIE ? 100 : 1);
};
/**
* Private initialization helpers
@@ -4409,11 +4798,11 @@
// flash path
var remoteURL = (smURL || _s.url),
localURL = (_s.altURL || remoteURL),
swfTitle = 'JS/Flash audio component (SoundManager 2)',
oEmbed, oMovie, oTarget = _getDocument(), tmp, movieHTML, oEl, extraClass = _getSWFCSS(),
- s, x, sClass, side = null, isRTL = null,
+ s, x, sClass, isRTL = null,
html = _doc.getElementsByTagName('html')[0];
isRTL = (html && html.dir && html.dir.match(/rtl/i));
smID = (typeof smID === 'undefined'?_s.id:smID);
@@ -4451,16 +4840,10 @@
'wmode': _s.wmode,
// http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
'hasPriority': 'true'
};
- if (side !== null) {
- // don't specify width/height if null.
- oEmbed.width = side;
- oEmbed.height = side;
- }
-
if (_s.debugFlash) {
oEmbed.FlashVars = 'debug=1';
}
if (!_s.wmode) {
@@ -4471,11 +4854,11 @@
if (_isIE) {
// IE is "special".
oMovie = _doc.createElement('div');
movieHTML = [
- '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + _http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0" width="' + oEmbed.width + '" height="' + oEmbed.height + '">',
+ '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + _http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',
param('movie', smURL),
param('AllowScriptAccess', _s.allowScriptAccess),
param('quality', oEmbed.quality),
(_s.wmode? param('wmode', _s.wmode): ''),
param('bgcolor', _s.bgColor),
@@ -4627,11 +5010,11 @@
if (_flash) {
_wDS('waitEI');
}
// </d>
- if (_s.oninitmovie instanceof Function) {
+ if (typeof _s.oninitmovie === 'function') {
setTimeout(_s.oninitmovie, 1);
}
return true;
@@ -4643,33 +5026,46 @@
};
_waitForEI = function() {
+ var p,
+ loadIncomplete = false;
+
if (_waitingForEI) {
return false;
}
_waitingForEI = true;
_event.remove(_win, 'load', _delayWaitForEI);
if (_tryInitOnFocus && !_isFocused) {
- // giant Safari 3.1 hack - assume mousemove = focus given lack of focus event
+ // Safari won't load flash in background tabs, only when focused.
_wDS('waitFocus');
return false;
}
- var p;
if (!_didInit) {
p = _s.getMoviePercent();
- _s._wD(_str('waitImpatient', (p === 100?' (SWF loaded)':(p > 0?' (SWF ' + p + '% loaded)':''))));
+ _s._wD(_str('waitImpatient', (p > 0 ? ' (SWF ' + p + '% loaded)' : '')));
+ if (p > 0 && p < 100) {
+ loadIncomplete = true;
+ }
}
setTimeout(function() {
- p = _s.getMoviePercent();
+ p = _s.getMoviePercent();
+ if (loadIncomplete) {
+ // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
+ _waitingForEI = false;
+ _s._wD(_str('waitSWF'));
+ _win.setTimeout(_delayWaitForEI, 1);
+ return false;
+ }
+
// <d>
if (!_didInit) {
_s._wD(_sm + ': No Flash response within expected time.\nLikely causes: ' + (p === 0?'Loading ' + _s.movieURL + ' may have failed (and/or Flash ' + _fV + '+ not present?), ':'') + 'Flash blocked or JS-Flash security error.' + (_s.debugFlash?' ' + _str('checkSWF'):''), 2);
if (!_overHTTP && p) {
_wDS('localFail', 2);
@@ -4715,29 +5111,28 @@
_handleFocus = function() {
function cleanup() {
_event.remove(_win, 'focus', _handleFocus);
- _event.remove(_win, 'load', _handleFocus);
}
if (_isFocused || !_tryInitOnFocus) {
+ // already focused, or not special Safari background tab case
cleanup();
return true;
}
_okToDisable = true;
_isFocused = true;
- _s._wD(_smc+'handleFocus()');
+ _s._wD(_sm+': Got window focus.');
- if (_isSafari && _tryInitOnFocus) {
- _event.remove(_win, 'mousemove', _handleFocus);
- }
-
// allow init to restart
_waitingForEI = false;
+ // kick off ExternalInterface timeout, now that the SWF has started
+ _delayWaitForEI();
+
cleanup();
return true;
};
@@ -4770,10 +5165,11 @@
_debugTS('onload', true);
return true;
}
var wasTimeout = (_s.useFlashBlock && _s.flashLoadTimeout && !_s.getMoviePercent()),
+ result = true,
error;
if (!wasTimeout) {
_didInit = true;
if (_disabled) {
@@ -4785,35 +5181,70 @@
if (_disabled || bNoDisable) {
if (_s.useFlashBlock && _s.oMC) {
_s.oMC.className = _getSWFCSS() + ' ' + (_s.getMoviePercent() === null?_swfCSS.swfTimedout:_swfCSS.swfError);
}
- _processOnEvents({type:'ontimeout', error:error});
+ _processOnEvents({type:'ontimeout', error:error, ignoreInit: true});
_debugTS('onload', false);
_catchError(error);
- return false;
+ result = false;
} else {
_debugTS('onload', true);
}
- if (_s.waitForWindowLoad && !_windowLoaded) {
- _wDS('waitOnload');
- _event.add(_win, 'load', _initUserOnload);
- return false;
- } else {
- // <d>
- if (_s.waitForWindowLoad && _windowLoaded) {
- _wDS('docLoaded');
+ if (!_disabled) {
+ if (_s.waitForWindowLoad && !_windowLoaded) {
+ _wDS('waitOnload');
+ _event.add(_win, 'load', _initUserOnload);
+ } else {
+ // <d>
+ if (_s.waitForWindowLoad && _windowLoaded) {
+ _wDS('docLoaded');
+ }
+ // </d>
+ _initUserOnload();
}
- // </d>
- _initUserOnload();
}
- return true;
+ return result;
};
+ /**
+ * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
+ * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
+ */
+
+ _setProperties = function() {
+
+ var i,
+ o = _s.setupOptions;
+
+ for (i in o) {
+
+ if (o.hasOwnProperty(i)) {
+
+ // assign local property if not already defined
+
+ if (typeof _s[i] === 'undefined') {
+
+ _s[i] = o[i];
+
+ } else if (_s[i] !== o[i]) {
+
+ // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
+ _s.setupOptions[i] = _s[i];
+
+ }
+
+ }
+
+ }
+
+ };
+
+
_init = function() {
_wDS('init');
// called after onload()
@@ -4892,47 +5323,61 @@
if (_didDCLoaded) {
return false;
}
_didDCLoaded = true;
+
+ // assign top-level soundManager properties eg. soundManager.url
+ _setProperties();
+
_initDebug();
/**
* Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1
* Ditto for sm2-preferFlash, too.
*/
// <d>
(function(){
- var a = 'sm2-usehtml5audio=', l = _wl.toLowerCase(), b = null,
- a2 = 'sm2-preferflash=', b2 = null, hasCon = (typeof console !== 'undefined' && typeof console.log !== 'undefined');
+ var a = 'sm2-usehtml5audio=',
+ a2 = 'sm2-preferflash=',
+ b = null,
+ b2 = null,
+ hasCon = (typeof console !== 'undefined' && typeof console.log === 'function'),
+ l = _wl.toLowerCase();
if (l.indexOf(a) !== -1) {
b = (l.charAt(l.indexOf(a)+a.length) === '1');
if (hasCon) {
console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
}
- _s.useHTML5Audio = b;
+ _s.setup({
+ 'useHTML5Audio': b
+ });
}
if (l.indexOf(a2) !== -1) {
b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');
if (hasCon) {
console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
}
- _s.preferFlash = b2;
+ _s.setup({
+ 'preferFlash': b2
+ });
}
}());
// </d>
if (!_hasFlash && _s.hasHTML5) {
_s._wD('SoundManager: No Flash detected'+(!_s.useHTML5Audio?', enabling HTML5.':'. Trying HTML5-only mode.'));
- _s.useHTML5Audio = true;
- // make sure we aren't preferring flash, either
- // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
- _s.preferFlash = false;
+ _s.setup({
+ 'useHTML5Audio': true,
+ // make sure we aren't preferring flash, either
+ // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
+ 'preferFlash': false
+ });
}
_testHTML5();
_s.html5.usingFlash = _featureCheck();
_needsFlash = _s.html5.usingFlash;
@@ -4940,11 +5385,13 @@
if (!_hasFlash && _needsFlash) {
_s._wD('SoundManager: Fatal error: Flash is needed to play some required formats, but is not available.');
// TODO: Fatal here vs. timeout approach, etc.
// hack: fail sooner.
- _s.flashLoadTimeout = 1;
+ _s.setup({
+ 'flashLoadTimeout': 1
+ });
}
if (_doc.removeEventListener) {
_doc.removeEventListener('DOMContentLoaded', _domContentLoaded, false);
}
@@ -4974,20 +5421,13 @@
// sniff up-front
_detectFlash();
// focus and window load, init (primarily flash-driven)
_event.add(_win, 'focus', _handleFocus);
- _event.add(_win, 'load', _handleFocus);
_event.add(_win, 'load', _delayWaitForEI);
_event.add(_win, 'load', _winOnLoad);
-
- if (_isSafari && _tryInitOnFocus) {
- // massive Safari 3.1 focus detection hack
- _event.add(_win, 'mousemove', _handleFocus);
- }
-
if (_doc.addEventListener) {
_doc.addEventListener('DOMContentLoaded', _domContentLoaded, false);
} else if (_doc.attachEvent) {
@@ -5021,6 +5461,6 @@
*/
window.SoundManager = SoundManager; // constructor
window.soundManager = soundManager; // public API, flash callbacks etc.
-}(window));
+}(window));
\ No newline at end of file