3 * SoundManager 2: JavaScript Sound for the Web
4 * ----------------------------------------------
5 * http://schillmania.com/projects/soundmanager2/
7 * Copyright (c) 2007, Scott Schiller. All rights reserved.
8 * Code provided under the BSD License:
9 * http://schillmania.com/projects/soundmanager2/license.txt
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, todo: true */
19 * -------------------------------------------------------------------------------------
20 * This is the fully-commented source version of the SoundManager 2 API,
21 * recommended for use during development and testing.
23 * See soundmanager2-nodebug-jsmin.js for an optimized build (~11KB with gzip.)
24 * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion
25 * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.)
27 * You may notice <d> and </d> comments in this source; these are delimiters for
28 * debug blocks which are removed in the -nodebug builds, further optimizing code size.
30 * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;)
33 (function(window
, _undefined
) {
37 var soundManager
= null;
40 * The SoundManager constructor.
43 * @param {string} smURL Optional: Path to SWF files
44 * @param {string} smID Optional: The ID to use for the SWF container element
45 * @this {SoundManager}
46 * @return {SoundManager} The new SoundManager instance
49 function SoundManager(smURL
, smID
) {
52 * soundManager configuration options list
53 * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)
54 * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})
59 'url': (smURL
|| null), // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/
60 'flashVersion': 8, // flash build to use (8 or 9.) Some API features require 9.
61 'debugMode': true, // enable debugging output (console.log() with HTML fallback)
62 'debugFlash': false, // enable debugging output inside SWF, troubleshoot Flash/browser issues
63 'useConsole': true, // use console.log() if available (otherwise, writes to #soundmanager-debug element)
64 'consoleOnly': true, // if console is being used, do not create/write to #soundmanager-debug
65 'waitForWindowLoad': false, // force SM2 to wait for window.onload() before trying to call soundManager.onload()
66 'bgColor': '#ffffff', // SWF background color. N/A when wmode = 'transparent'
67 'useHighPerformance': false, // position:fixed flash movie can help increase js/flash speed, minimize lag
68 'flashPollingInterval': null, // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
69 'html5PollingInterval': null, // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
70 'flashLoadTimeout': 1000, // msec to wait for flash movie to load before failing (0 = infinity)
71 'wmode': null, // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
72 'allowScriptAccess': 'always', // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
73 'useFlashBlock': false, // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
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 'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
76 'preferFlash': false, // overrides useHTML5audio, will use Flash for MP3/MP4/AAC if present. Potential option if HTML5 playback with these formats is quirky.
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.
82 this.defaultOptions
= {
85 * the default configuration for sound objects made with createSound() and related methods
86 * eg., volume, auto-load behaviour and so forth
89 'autoLoad': false, // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
90 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true)
91 'from': null, // position to start playback within a sound (msec), default = beginning
92 'loops': 1, // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)
93 'onid3': null, // callback function for "ID3 data is added/available"
94 'onload': null, // callback function for "load finished"
95 'whileloading': null, // callback function for "download progress update" (X of Y bytes received)
96 'onplay': null, // callback for "play" start
97 'onpause': null, // callback for "pause"
98 'onresume': null, // callback for "resume" (pause toggle)
99 'whileplaying': null, // callback during play (position update)
100 'onposition': null, // object containing times and function callbacks for positions of interest
101 'onstop': null, // callback for "user stop"
102 'onfailure': null, // callback function for when playing fails
103 'onfinish': null, // callback function for "sound finished playing"
104 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
105 'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled
106 'position': null, // offset (milliseconds) to seek to within loaded sound data.
107 'pan': 0, // "pan" settings, left-to-right, -100 to 100
108 'stream': true, // allows playing before entire file has loaded (recommended)
109 'to': null, // position to end playback within a sound (msec), default = end
110 'type': null, // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3
111 'usePolicyFile': false, // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)
112 'volume': 100 // self-explanatory. 0-100, the latter being the max.
116 this.flash9Options
= {
119 * flash 9-only options,
120 * merged into defaultOptions if flash 9 is being used
123 'isMovieStar': null, // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL
124 'usePeakData': false, // enable left/right channel peak (level) data
125 'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.
126 'useEQData': false, // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.
127 'onbufferchange': null, // callback for "isBuffering" property change
128 'ondataerror': null // callback for waveform/eq data access error (flash playing audio in other tabs/domains)
132 this.movieStarOptions
= {
135 * flash 9.0r115+ MPEG4 audio options,
136 * merged into defaultOptions if flash 9+movieStar mode is enabled
139 'bufferTime': 3, // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)
140 'serverURL': null, // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants
141 'onconnect': null, // rtmp: callback for connection to flash media server
142 'duration': null // rtmp: song duration (msec)
146 this.audioFormats
= {
149 * determines HTML5 support + flash requirements.
150 * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
151 * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
155 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
160 'related': ['aac','m4a','m4b'], // additional formats under the MP4 container
161 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
166 'type': ['audio/ogg; codecs=vorbis'],
171 'type': ['audio/ogg; codecs=opus', 'audio/opus'],
176 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
182 // HTML attributes (id + class names) for the SWF container
184 this.movieID
= 'sm2-container';
185 this.id
= (smID
|| 'sm2movie');
187 this.debugID
= 'soundmanager-debug';
188 this.debugURLParam
= /([#?&])debug=1/i;
190 // dynamic attributes
192 this.versionNumber
= 'V2.97a.20131201';
194 this.movieURL
= null;
196 this.swfLoaded
= false;
197 this.enabled
= false;
202 this.didFlashBlock
= false;
203 this.filePattern
= null;
205 this.filePatterns
= {
207 'flash8': /\.mp3(\?.*)?$/i,
208 'flash9': /\.mp3(\?.*)?$/i
212 // support indicators, set at init
218 'waveformData': false,
224 // flash sandbox info, used primarily in troubleshooting
231 'remote': 'remote (domain-based) rules',
232 'localWithFile': 'local with file access (no internet access)',
233 'localWithNetwork': 'local with network (internet access only, no local access)',
234 'localTrusted': 'local, trusted (local+internet access)'
244 * format support (html5/flash)
245 * stores canPlayType() results based on audioFormats.
246 * eg. { mp3: boolean, mp4: boolean }
247 * treat as read-only.
251 'usingFlash': null // set if/when flash fallback is needed
254 // file type support hash
257 // determined at init time
258 this.html5Only
= false;
260 // used for special cases (eg. iPad/iPhone/palm OS?)
261 this.ignoreFlash
= false;
264 * a few private internals (OK, a lot. :D)
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
, rebootIntoHTML5
, 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
= [],
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)),
271 mobileHTML5
= (ua
.match(/(mobile|pre\/|xoom)/i) || is_iDevice
|| isAndroid
),
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
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,
274 emptyURL
= 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
275 emptyWAV
= 'data:audio/wave;base64,/UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAD//w==', // tiny WAV for HTML5 unloading
276 overHTTP
= (doc
.location
?doc
.location
.protocol
.match(/http/i):null),
277 http
= (!overHTTP
? 'http:/'+'/' : ''),
278 // mp3, mp4, aac etc.
279 netStreamMimeTypes
= /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,
280 // Flash v9.0r115+ "moviestar" formats
281 netStreamTypes
= ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],
282 netStreamPattern
= new RegExp('\\.(' + netStreamTypes
.join('|') + ')(\\?.*)?$', 'i');
284 this.mimePattern
= /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set
286 // use altURL if not "online"
287 this.useAltURL
= !overHTTP
;
291 'swfBox': 'sm2-object-box',
292 'swfDefault': 'movieContainer',
293 'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)
294 'swfTimedout': 'swf_timedout',
295 'swfLoaded': 'swf_loaded',
296 'swfUnblocked': 'swf_unblocked', // or loaded OK
297 'sm2Debug': 'sm2_debug',
298 'highPerf': 'high_performance',
299 'flashDebug': 'flash_debug'
304 * basic HTML5 Audio() support test
305 * try...catch because of IE 9 "not implemented" nonsense
306 * https://github.com/Modernizr/Modernizr/issues/224
309 this.hasHTML5
= (function() {
311 // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.
312 return (Audio
!== _undefined
&& (isOpera
&& opera
!== _undefined
&& opera
.version() < 10 ? new Audio(null) : new Audio()).canPlayType
!== _undefined
);
319 * Public SoundManager API
320 * -----------------------
324 * Configures top-level soundManager properties.
326 * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
327 * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
330 this.setup = function(options
) {
332 var noURL
= (!sm2
.url
);
334 // warn if flash options have already been applied
336 if (options
!== _undefined
&& didInit
&& needsFlash
&& sm2
.ok() && (options
.flashVersion
!== _undefined
|| options
.url
!== _undefined
|| options
.html5Test
!== _undefined
)) {
337 complain(str('setupLate'));
340 // TODO: defer: true?
344 // 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.
348 if (noURL
&& didDCLoaded
&& options
.url
!== _undefined
) {
349 sm2
.beginDelayedInit();
352 // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
354 if (!didDCLoaded
&& options
.url
!== _undefined
&& doc
.readyState
=== 'complete') {
355 setTimeout(domContentLoaded
, 1);
364 this.ok = function() {
366 return (needsFlash
? (didInit
&& !disabled
) : (sm2
.useHTML5Audio
&& sm2
.hasHTML5
));
370 this.supported
= this.ok
; // legacy
372 this.getMovie = function(smID
) {
374 // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version
375 return id(smID
) || doc
[smID
] || window
[smID
];
380 * Creates a SMSound sound object instance.
382 * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
383 * @return {object} SMSound The new SMSound object.
386 this.createSound = function(oOptions
, _url
) {
388 var cs
, cs_string
, options
, oSound
= null;
391 cs
= sm
+ '.createSound(): ';
392 cs_string
= cs
+ str(!didInit
?'notReady':'notOK');
395 if (!didInit
|| !sm2
.ok()) {
400 if (_url
!== _undefined
) {
401 // function overloading in JS! :) ..assume simple createSound(id, url) use case
408 // inherit from defaultOptions
409 options
= mixin(oOptions
);
411 options
.url
= parseURL(options
.url
);
413 // generate an id, if needed.
414 if (options
.id
=== undefined) {
415 options
.id
= sm2
.setupOptions
.idPrefix
+ (idCounter
++);
419 if (options
.id
.toString().charAt(0).match(/^[0-9]$/)) {
420 sm2
._wD(cs
+ str('badID', options
.id
), 2);
423 sm2
._wD(cs
+ options
.id
+ (options
.url
? ' (' + options
.url
+ ')' : ''), 1);
426 if (idCheck(options
.id
, true)) {
427 sm2
._wD(cs
+ options
.id
+ ' exists', 1);
428 return sm2
.sounds
[options
.id
];
433 options
= loopFix(options
);
434 sm2
.sounds
[options
.id
] = new SMSound(options
);
435 sm2
.soundIDs
.push(options
.id
);
436 return sm2
.sounds
[options
.id
];
440 if (html5OK(options
)) {
443 sm2
._wD(options
.id
+ ': Using HTML5');
444 oSound
._setup_html5(options
);
449 sm2
._wD(options
.id
+ ': No HTML5 support for this sound, and no Flash. Exiting.');
453 // TODO: Move HTML5/flash checks into generic URL parsing/handling function.
455 if (sm2
.html5
.usingFlash
&& options
.url
&& options
.url
.match(/data\:/i)) {
456 // data: URIs not supported by Flash, either.
457 sm2
._wD(options
.id
+ ': data: URIs not supported via Flash. Exiting.');
462 if (options
.isMovieStar
=== null) {
463 // attempt to detect MPEG-4 formats
464 options
.isMovieStar
= !!(options
.serverURL
|| (options
.type
? options
.type
.match(netStreamMimeTypes
) : false) || (options
.url
&& options
.url
.match(netStreamPattern
)));
467 if (options
.isMovieStar
) {
468 sm2
._wD(cs
+ 'using MovieStar handling');
469 if (options
.loops
> 1) {
476 options
= policyFix(options
, cs
);
480 flash
._createSound(options
.id
, options
.loops
||1, options
.usePolicyFile
);
482 flash
._createSound(options
.id
, options
.url
, options
.usePeakData
, options
.useWaveformData
, options
.useEQData
, options
.isMovieStar
, (options
.isMovieStar
?options
.bufferTime:false), options
.loops
||1, options
.serverURL
, options
.duration
||null, options
.autoPlay
, true, options
.autoLoad
, options
.usePolicyFile
);
483 if (!options
.serverURL
) {
484 // We are connected immediately
485 oSound
.connected
= true;
486 if (options
.onconnect
) {
487 options
.onconnect
.apply(oSound
);
492 if (!options
.serverURL
&& (options
.autoLoad
|| options
.autoPlay
)) {
493 // call load for non-rtmp streams
494 oSound
.load(options
);
499 // rtmp will play in onconnect
500 if (!options
.serverURL
&& options
.autoPlay
) {
509 * Destroys a SMSound sound object instance.
511 * @param {string} sID The ID of the sound to destroy
514 this.destroySound = function(sID
, _bFromSound
) {
516 // explicitly destroy a sound before normal page unload, etc.
522 var oS
= sm2
.sounds
[sID
], i
;
524 // Disable all callbacks while the sound is being destroyed
530 for (i
= 0; i
< sm2
.soundIDs
.length
; i
++) {
531 if (sm2
.soundIDs
[i
] === sID
) {
532 sm2
.soundIDs
.splice(i
, 1);
538 // ignore if being called from SMSound instance
543 delete sm2
.sounds
[sID
];
550 * Calls the load() method of a SMSound object by ID.
552 * @param {string} sID The ID of the sound
553 * @param {object} oOptions Optional: Sound options
556 this.load = function(sID
, oOptions
) {
561 return sm2
.sounds
[sID
].load(oOptions
);
566 * Calls the unload() method of a SMSound object by ID.
568 * @param {string} sID The ID of the sound
571 this.unload = function(sID
) {
576 return sm2
.sounds
[sID
].unload();
581 * Calls the onPosition() method of a SMSound object by ID.
583 * @param {string} sID The ID of the sound
584 * @param {number} nPosition The position to watch for
585 * @param {function} oMethod The relevant callback to fire
586 * @param {object} oScope Optional: The scope to apply the callback to
587 * @return {SMSound} The SMSound object
590 this.onPosition = function(sID
, nPosition
, oMethod
, oScope
) {
595 return sm2
.sounds
[sID
].onposition(nPosition
, oMethod
, oScope
);
599 // legacy/backwards-compability: lower-case method name
600 this.onposition
= this.onPosition
;
603 * Calls the clearOnPosition() method of a SMSound object by ID.
605 * @param {string} sID The ID of the sound
606 * @param {number} nPosition The position to watch for
607 * @param {function} oMethod Optional: The relevant callback to fire
608 * @return {SMSound} The SMSound object
611 this.clearOnPosition = function(sID
, nPosition
, oMethod
) {
616 return sm2
.sounds
[sID
].clearOnPosition(nPosition
, oMethod
);
621 * Calls the play() method of a SMSound object by ID.
623 * @param {string} sID The ID of the sound
624 * @param {object} oOptions Optional: Sound options
625 * @return {SMSound} The SMSound object
628 this.play = function(sID
, oOptions
) {
631 // legacy function-overloading use case: play('mySound', '/path/to/some.mp3');
632 overloaded
= (oOptions
&& !(oOptions
instanceof Object
));
634 if (!didInit
|| !sm2
.ok()) {
635 complain(sm
+ '.play(): ' + str(!didInit
?'notReady':'notOK'));
639 if (!idCheck(sID
, overloaded
)) {
642 // no sound found for the given ID. Bail.
652 if (oOptions
&& oOptions
.url
) {
653 // overloading use case, create+play: .play('someID', {url:'/path/to.mp3'});
654 sm2
._wD(sm
+ '.play(): Attempting to create "' + sID
+ '"', 1);
656 result
= sm2
.createSound(oOptions
).play();
659 } else if (overloaded
) {
661 // existing sound object case
668 if (result
=== null) {
670 result
= sm2
.sounds
[sID
].play(oOptions
);
677 this.start
= this.play
; // just for convenience
680 * Calls the setPosition() method of a SMSound object by ID.
682 * @param {string} sID The ID of the sound
683 * @param {number} nMsecOffset Position (milliseconds)
684 * @return {SMSound} The SMSound object
687 this.setPosition = function(sID
, nMsecOffset
) {
692 return sm2
.sounds
[sID
].setPosition(nMsecOffset
);
697 * Calls the stop() method of a SMSound object by ID.
699 * @param {string} sID The ID of the sound
700 * @return {SMSound} The SMSound object
703 this.stop = function(sID
) {
709 sm2
._wD(sm
+ '.stop(' + sID
+ ')', 1);
710 return sm2
.sounds
[sID
].stop();
715 * Stops all currently-playing sounds.
718 this.stopAll = function() {
721 sm2
._wD(sm
+ '.stopAll()', 1);
723 for (oSound
in sm2
.sounds
) {
724 if (sm2
.sounds
.hasOwnProperty(oSound
)) {
725 // apply only to sound objects
726 sm2
.sounds
[oSound
].stop();
733 * Calls the pause() method of a SMSound object by ID.
735 * @param {string} sID The ID of the sound
736 * @return {SMSound} The SMSound object
739 this.pause = function(sID
) {
744 return sm2
.sounds
[sID
].pause();
749 * Pauses all currently-playing sounds.
752 this.pauseAll = function() {
755 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
756 sm2
.sounds
[sm2
.soundIDs
[i
]].pause();
762 * Calls the resume() method of a SMSound object by ID.
764 * @param {string} sID The ID of the sound
765 * @return {SMSound} The SMSound object
768 this.resume = function(sID
) {
773 return sm2
.sounds
[sID
].resume();
778 * Resumes all currently-paused sounds.
781 this.resumeAll = function() {
784 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
785 sm2
.sounds
[sm2
.soundIDs
[i
]].resume();
791 * Calls the togglePause() method of a SMSound object by ID.
793 * @param {string} sID The ID of the sound
794 * @return {SMSound} The SMSound object
797 this.togglePause = function(sID
) {
802 return sm2
.sounds
[sID
].togglePause();
807 * Calls the setPan() method of a SMSound object by ID.
809 * @param {string} sID The ID of the sound
810 * @param {number} nPan The pan value (-100 to 100)
811 * @return {SMSound} The SMSound object
814 this.setPan = function(sID
, nPan
) {
819 return sm2
.sounds
[sID
].setPan(nPan
);
824 * Calls the setVolume() method of a SMSound object by ID.
826 * @param {string} sID The ID of the sound
827 * @param {number} nVol The volume value (0 to 100)
828 * @return {SMSound} The SMSound object
831 this.setVolume = function(sID
, nVol
) {
836 return sm2
.sounds
[sID
].setVolume(nVol
);
841 * Calls the mute() method of either a single SMSound object by ID, or all sound objects.
843 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
846 this.mute = function(sID
) {
850 if (sID
instanceof String
) {
856 sm2
._wD(sm
+ '.mute(): Muting all sounds');
857 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
858 sm2
.sounds
[sm2
.soundIDs
[i
]].mute();
867 sm2
._wD(sm
+ '.mute(): Muting "' + sID
+ '"');
868 return sm2
.sounds
[sID
].mute();
880 this.muteAll = function() {
887 * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.
889 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
892 this.unmute = function(sID
) {
896 if (sID
instanceof String
) {
902 sm2
._wD(sm
+ '.unmute(): Unmuting all sounds');
903 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
904 sm2
.sounds
[sm2
.soundIDs
[i
]].unmute();
913 sm2
._wD(sm
+ '.unmute(): Unmuting "' + sID
+ '"');
914 return sm2
.sounds
[sID
].unmute();
923 * Unmutes all sounds.
926 this.unmuteAll = function() {
933 * Calls the toggleMute() method of a SMSound object by ID.
935 * @param {string} sID The ID of the sound
936 * @return {SMSound} The SMSound object
939 this.toggleMute = function(sID
) {
944 return sm2
.sounds
[sID
].toggleMute();
949 * Retrieves the memory used by the flash plugin.
951 * @return {number} The amount of memory in use
954 this.getMemoryUse = function() {
959 if (flash
&& fV
!== 8) {
960 ram
= parseInt(flash
._getMemoryUse(), 10);
968 * Undocumented: NOPs soundManager and all SMSound objects.
971 this.disable = function(bNoDisable
) {
973 // destroy all functions
976 if (bNoDisable
=== _undefined
) {
987 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
988 disableObject(sm2
.sounds
[sm2
.soundIDs
[i
]]);
991 // fire "complete", despite fail
992 initComplete(bNoDisable
);
993 event
.remove(window
, 'load', initUserOnload
);
1000 * Determines playability of a MIME type, eg. 'audio/mp3'.
1003 this.canPlayMIME = function(sMIME
) {
1008 result
= html5CanPlay({type:sMIME
});
1011 if (!result
&& needsFlash
) {
1012 // if flash 9, test netStream (movieStar) types as well.
1013 result
= (sMIME
&& sm2
.ok() ? !!((fV
> 8 ? sMIME
.match(netStreamMimeTypes
) : null) || sMIME
.match(sm2
.mimePattern
)) : null); // TODO: make less "weird" (per JSLint)
1021 * Determines playability of a URL based on audio support.
1023 * @param {string} sURL The URL to test
1024 * @return {boolean} URL playability
1027 this.canPlayURL = function(sURL
) {
1032 result
= html5CanPlay({url: sURL
});
1035 if (!result
&& needsFlash
) {
1036 result
= (sURL
&& sm2
.ok() ? !!(sURL
.match(sm2
.filePattern
)) : null);
1044 * Determines playability of an HTML DOM <a> object (or similar object literal) based on audio support.
1046 * @param {object} oLink an HTML DOM <a> object or object literal including href and/or type attributes
1047 * @return {boolean} URL playability
1050 this.canPlayLink = function(oLink
) {
1052 if (oLink
.type
!== _undefined
&& oLink
.type
) {
1053 if (sm2
.canPlayMIME(oLink
.type
)) {
1058 return sm2
.canPlayURL(oLink
.href
);
1063 * Retrieves a SMSound object by ID.
1065 * @param {string} sID The ID of the sound
1066 * @return {SMSound} The SMSound object
1069 this.getSoundById = function(sID
, _suppressDebug
) {
1075 var result
= sm2
.sounds
[sID
];
1078 if (!result
&& !_suppressDebug
) {
1079 sm2
._wD(sm
+ '.getSoundById(): Sound "' + sID
+ '" not found.', 2);
1088 * Queues a callback for execution when SoundManager has successfully initialized.
1090 * @param {function} oMethod The callback method to fire
1091 * @param {object} oScope Optional: The scope to apply to the callback
1094 this.onready = function(oMethod
, oScope
) {
1096 var sType
= 'onready',
1099 if (typeof oMethod
=== 'function') {
1103 sm2
._wD(str('queue', sType
));
1111 addOnEvent(sType
, oMethod
, oScope
);
1118 throw str('needFunction', sType
);
1127 * Queues a callback for execution when SoundManager has failed to initialize.
1129 * @param {function} oMethod The callback method to fire
1130 * @param {object} oScope Optional: The scope to apply to the callback
1133 this.ontimeout = function(oMethod
, oScope
) {
1135 var sType
= 'ontimeout',
1138 if (typeof oMethod
=== 'function') {
1142 sm2
._wD(str('queue', sType
));
1150 addOnEvent(sType
, oMethod
, oScope
);
1151 processOnEvents({type:sType
});
1157 throw str('needFunction', sType
);
1166 * Writes console.log()-style debug output to a console or in-browser element.
1167 * Applies when debugMode = true
1169 * @param {string} sText The console message
1170 * @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.
1173 this._writeDebug = function(sText
, sTypeOrObject
) {
1175 // pseudo-private console.log()-style output
1178 var sDID
= 'soundmanager-debug', o
, oItem
;
1180 if (!sm2
.debugMode
) {
1184 if (hasConsole
&& sm2
.useConsole
) {
1185 if (sTypeOrObject
&& typeof sTypeOrObject
=== 'object') {
1186 // object passed; dump to console.
1187 console
.log(sText
, sTypeOrObject
);
1188 } else if (debugLevels
[sTypeOrObject
] !== _undefined
) {
1189 console
[debugLevels
[sTypeOrObject
]](sText
);
1193 if (sm2
.consoleOnly
) {
1204 oItem
= doc
.createElement('div');
1206 if (++wdCount
% 2 === 0) {
1207 oItem
.className
= 'sm2-alt';
1210 if (sTypeOrObject
=== _undefined
) {
1213 sTypeOrObject
= parseInt(sTypeOrObject
, 10);
1216 oItem
.appendChild(doc
.createTextNode(sText
));
1218 if (sTypeOrObject
) {
1219 if (sTypeOrObject
>= 2) {
1220 oItem
.style
.fontWeight
= 'bold';
1222 if (sTypeOrObject
=== 3) {
1223 oItem
.style
.color
= '#ff3333';
1228 // o.appendChild(oItem);
1231 o
.insertBefore(oItem
, o
.firstChild
);
1241 // last-resort debugging option
1242 if (wl
.indexOf('sm2-debug=alert') !== -1) {
1243 this._writeDebug = function(sText
) {
1244 window
.alert(sText
);
1250 this._wD
= this._writeDebug
;
1253 * Provides debug / state information on all SMSound objects.
1256 this._debug = function() {
1260 _wDS('currentObj', 1);
1262 for (i
= 0, j
= sm2
.soundIDs
.length
; i
< j
; i
++) {
1263 sm2
.sounds
[sm2
.soundIDs
[i
]]._debug();
1270 * Restarts and re-initializes the SoundManager instance.
1272 * @param {boolean} resetEvents Optional: When true, removes all registered onready and ontimeout event callbacks.
1273 * @param {boolean} excludeInit Options: When true, does not call beginDelayedInit() (which would restart SM2).
1274 * @return {object} soundManager The soundManager instance.
1277 this.reboot = function(resetEvents
, excludeInit
) {
1279 // reset some (or all) state, and re-init unless otherwise specified.
1282 if (sm2
.soundIDs
.length
) {
1283 sm2
._wD('Destroying ' + sm2
.soundIDs
.length
+ ' SMSound object' + (sm2
.soundIDs
.length
!== 1 ? 's' : '') + '...');
1289 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
1290 sm2
.sounds
[sm2
.soundIDs
[i
]].destruct();
1293 // trash ze flash (remove from the DOM)
1300 oRemovedHTML
= flash
.innerHTML
;
1303 oRemoved
= flash
.parentNode
.removeChild(flash
);
1307 // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.
1309 _wDS('badRemove', 2);
1315 // actually, force recreate of movie.
1317 oRemovedHTML
= oRemoved
= needsFlash
= flash
= null;
1319 sm2
.enabled
= didDCLoaded
= didInit
= waitingForEI
= initPending
= didAppend
= appendSuccess
= disabled
= useGlobalHTML5Audio
= sm2
.swfLoaded
= false;
1327 // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init
1328 for (i
in on_queue
) {
1329 if (on_queue
.hasOwnProperty(i
)) {
1330 for (j
= 0, k
= on_queue
[i
].length
; j
< k
; j
++) {
1331 on_queue
[i
][j
].fired
= false;
1336 // remove all callbacks entirely
1342 sm2
._wD(sm
+ ': Rebooting...');
1346 // reset HTML5 and flash canPlay test results
1354 // reset device-specific HTML/flash mode switches
1356 sm2
.html5Only
= false;
1357 sm2
.ignoreFlash
= false;
1359 window
.setTimeout(function() {
1363 // by default, re-init
1366 sm2
.beginDelayedInit();
1375 this.reset = function() {
1378 * Shuts down and restores the SoundManager instance to its original loaded state, without an explicit reboot. All onready/ontimeout handlers are removed.
1379 * After this call, SM2 may be re-initialized via soundManager.beginDelayedInit().
1380 * @return {object} soundManager The soundManager instance.
1384 return sm2
.reboot(true, true);
1389 * Undocumented: Determines the SM2 flash movie's load progress.
1391 * @return {number or null} Percent loaded, or if invalid/unsupported, null.
1394 this.getMoviePercent = function() {
1397 * Interesting syntax notes...
1398 * Flash/ExternalInterface (ActiveX/NPAPI) bridge methods are not typeof "function" nor instanceof Function, but are still valid.
1399 * Additionally, JSLint dislikes ('PercentLoaded' in flash)-style syntax and recommends hasOwnProperty(), which does not work in this case.
1400 * Furthermore, using (flash && flash.PercentLoaded) causes IE to throw "object doesn't support this property or method".
1401 * Thus, 'in' syntax must be used.
1404 return (flash
&& 'PercentLoaded' in flash
? flash
.PercentLoaded() : null); // Yes, JSLint. See nearby comment in source for explanation.
1409 * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().
1412 this.beginDelayedInit = function() {
1414 windowLoaded
= true;
1417 setTimeout(function() {
1436 * Destroys the SoundManager instance and all SMSound instances.
1439 this.destruct = function() {
1441 sm2
._wD(sm
+ '.destruct()');
1447 * SMSound() (sound object) constructor
1448 * ------------------------------------
1450 * @param {object} oOptions Sound options (id and url are required attributes)
1451 * @return {SMSound} The new SMSound object
1454 SMSound = function(oOptions
) {
1456 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
;
1459 // tracks duration + position (time)
1464 this.id
= oOptions
.id
;
1469 this.url
= oOptions
.url
;
1470 this.options
= mixin(oOptions
);
1472 // per-play-instance-specific options
1473 this.instanceOptions
= this.options
;
1476 this._iO
= this.instanceOptions
;
1478 // assign property defaults
1479 this.pan
= this.options
.pan
;
1480 this.volume
= this.options
.volume
;
1482 // whether or not this object is using HTML5
1483 this.isHTML5
= false;
1485 // internal HTML5 Audio() object reference
1488 // for flash 8 special-case createSound() without url, followed by load/play with url case
1489 urlOmitted
= (this.url
? false : true);
1492 * SMSound() public methods
1493 * ------------------------
1499 * Writes SMSound object parameters to debug console
1502 this._debug = function() {
1505 sm2
._wD(s
.id
+ ': Merged options:', s
.options
);
1511 * Begins loading a sound per its *url*.
1513 * @param {object} oOptions Optional: Sound options
1514 * @return {SMSound} The SMSound object
1517 this.load = function(oOptions
) {
1519 var oSound
= null, instanceOptions
;
1521 if (oOptions
!== _undefined
) {
1522 s
._iO
= mixin(oOptions
, s
.options
);
1524 oOptions
= s
.options
;
1526 if (lastURL
&& lastURL
!== s
.url
) {
1537 s
._iO
.url
= parseURL(s
._iO
.url
);
1539 // ensure we're in sync
1540 s
.instanceOptions
= s
._iO
;
1543 instanceOptions
= s
._iO
;
1545 sm2
._wD(s
.id
+ ': load (' + instanceOptions
.url
+ ')');
1547 if (!instanceOptions
.url
&& !s
.url
) {
1548 sm2
._wD(s
.id
+ ': load(): url is unassigned. Exiting.', 2);
1553 if (!s
.isHTML5
&& fV
=== 8 && !s
.url
&& !instanceOptions
.autoPlay
) {
1554 // flash 8 load() -> play() won't work before onload has fired.
1555 sm2
._wD(s
.id
+ ': Flash 8 load() limitation: Wait for onload() before calling play().', 1);
1559 if (instanceOptions
.url
=== s
.url
&& s
.readyState
!== 0 && s
.readyState
!== 2) {
1561 // if loaded and an onload() exists, fire immediately.
1562 if (s
.readyState
=== 3 && instanceOptions
.onload
) {
1563 // assume success based on truthy duration.
1564 wrapCallback(s
, function() {
1565 instanceOptions
.onload
.apply(s
, [(!!s
.duration
)]);
1571 // reset a few state properties
1578 // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
1580 if (html5OK(instanceOptions
)) {
1582 oSound
= s
._setup_html5(instanceOptions
);
1584 if (!oSound
._called_load
) {
1586 s
._html5_canplay
= false;
1588 // TODO: review called_load / html5_canplay logic
1590 // if url provided directly to load(), assign it here.
1592 if (s
.url
!== instanceOptions
.url
) {
1594 sm2
._wD(_wDS('manURL') + ': ' + instanceOptions
.url
);
1596 s
._a
.src
= instanceOptions
.url
;
1598 // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
1600 // reset position for new URL
1605 // given explicit load call, try to preload.
1607 // early HTML5 implementation (non-standard)
1608 s
._a
.autobuffer
= 'auto';
1610 // standard property, values: none / metadata / auto
1611 // reference: http://msdn.microsoft.com/en-us/library/ie/ff974759%28v=vs.85%29.aspx
1612 s
._a
.preload
= 'auto';
1614 s
._a
._called_load
= true;
1618 sm2
._wD(s
.id
+ ': Ignoring request to load again');
1624 if (sm2
.html5Only
) {
1625 sm2
._wD(s
.id
+ ': No flash support. Exiting.');
1629 if (s
._iO
.url
&& s
._iO
.url
.match(/data\:/i)) {
1630 // data: URIs not supported by Flash, either.
1631 sm2
._wD(s
.id
+ ': data: URIs not supported via Flash. Exiting.');
1637 s
._iO
= policyFix(loopFix(instanceOptions
));
1638 // re-assign local shortcut
1639 instanceOptions
= s
._iO
;
1641 flash
._load(s
.id
, instanceOptions
.url
, instanceOptions
.stream
, instanceOptions
.autoPlay
, instanceOptions
.usePolicyFile
);
1643 flash
._load(s
.id
, instanceOptions
.url
, !!(instanceOptions
.stream
), !!(instanceOptions
.autoPlay
), instanceOptions
.loops
||1, !!(instanceOptions
.autoLoad
), instanceOptions
.usePolicyFile
);
1647 debugTS('onload', false);
1648 catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});
1653 // after all of this, ensure sound url is up to date.
1654 s
.url
= instanceOptions
.url
;
1661 * Unloads a sound, canceling any open HTTP requests.
1663 * @return {SMSound} The SMSound object
1666 this.unload = function() {
1668 // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL
1669 // Flash 9/AS3: Close stream, preventing further load
1670 // HTML5: Most UAs will use empty URL
1672 if (s
.readyState
!== 0) {
1674 sm2
._wD(s
.id
+ ': unload()');
1679 flash
._unload(s
.id
, emptyURL
);
1681 flash
._unload(s
.id
);
1692 // update empty URL, too
1693 lastURL
= html5Unload(s
._a
);
1699 // reset load/status flags
1709 * Unloads and destroys a sound.
1712 this.destruct = function(_bFromSM
) {
1714 sm2
._wD(s
.id
+ ': Destruct');
1718 // kill sound within Flash
1719 // Disable the onfailure handler
1720 s
._iO
.onfailure
= null;
1721 flash
._destroySound(s
.id
);
1730 if (!useGlobalHTML5Audio
) {
1731 remove_html5_events();
1733 // break obvious circular reference
1741 // ensure deletion from controller
1742 sm2
.destroySound(s
.id
, true);
1748 * Begins playing a sound.
1750 * @param {object} oOptions Optional: Sound options
1751 * @return {SMSound} The SMSound object
1754 this.play = function(oOptions
, _updatePlayState
) {
1756 var fN
, allowMulti
, a
, onready
,
1757 audioClone
, onended
, oncanplay
,
1762 fN
= s
.id
+ ': play(): ';
1766 _updatePlayState
= (_updatePlayState
=== _undefined
? true : _updatePlayState
);
1772 // first, use local URL (if specified)
1777 // mix in any options defined at createSound()
1778 s
._iO
= mixin(s
._iO
, s
.options
);
1780 // mix in any options specific to this method
1781 s
._iO
= mixin(oOptions
, s
._iO
);
1783 s
._iO
.url
= parseURL(s
._iO
.url
);
1785 s
.instanceOptions
= s
._iO
;
1788 if (!s
.isHTML5
&& s
._iO
.serverURL
&& !s
.connected
) {
1789 if (!s
.getAutoPlay()) {
1790 sm2
._wD(fN
+' Netstream not connected yet - setting autoPlay');
1791 s
.setAutoPlay(true);
1793 // play will be called in onconnect()
1797 if (html5OK(s
._iO
)) {
1798 s
._setup_html5(s
._iO
);
1799 start_html5_timer();
1802 if (s
.playState
=== 1 && !s
.paused
) {
1803 allowMulti
= s
._iO
.multiShot
;
1805 sm2
._wD(fN
+ 'Already playing (one-shot)', 1);
1807 // go back to original position.
1808 s
.setPosition(s
._iO
.position
);
1812 sm2
._wD(fN
+ 'Already playing (multi-shot)', 1);
1816 if (exit
!== null) {
1820 // edge case: play() with explicit URL parameter
1821 if (oOptions
.url
&& oOptions
.url
!== s
.url
) {
1823 // special case for createSound() followed by load() / play() with url; avoid double-load case.
1824 if (!s
.readyState
&& !s
.isHTML5
&& fV
=== 8 && urlOmitted
) {
1830 // load using merged options
1839 if (s
.readyState
=== 0) {
1841 sm2
._wD(fN
+ 'Attempting to load');
1843 // try to get this sound playing ASAP
1844 if (!s
.isHTML5
&& !sm2
.html5Only
) {
1846 // flash: assign directly because setAutoPlay() increments the instanceCount
1847 s
._iO
.autoPlay
= true;
1850 } else if (s
.isHTML5
) {
1852 // iOS needs this when recycling sounds, loading a new URL on an existing object.
1857 sm2
._wD(fN
+ 'Unsupported type. Exiting.');
1862 // HTML5 hack - re-set instanceOptions?
1863 s
.instanceOptions
= s
._iO
;
1865 } else if (s
.readyState
=== 2) {
1867 sm2
._wD(fN
+ 'Could not load - exiting', 2);
1872 sm2
._wD(fN
+ 'Loading - attempting to play...');
1879 sm2
._wD(fN
.substr(0, fN
.lastIndexOf(':')));
1883 if (exit
!== null) {
1887 if (!s
.isHTML5
&& fV
=== 9 && s
.position
> 0 && s
.position
=== s
.duration
) {
1888 // flash 9 needs a position reset if play() is called while at the end of a sound.
1889 sm2
._wD(fN
+ 'Sound at end, resetting to position:0');
1890 oOptions
.position
= 0;
1894 * Streams will pause when their buffer is full if they are being loaded.
1895 * In this case paused is true, but the song hasn't started playing yet.
1896 * If we just call resume() the onplay() callback will never be called.
1897 * So only call resume() if the position is > 0.
1898 * Another reason is because options like volume won't have been applied yet.
1899 * For normal sounds, just resume.
1902 if (s
.paused
&& s
.position
>= 0 && (!s
._iO
.serverURL
|| s
.position
> 0)) {
1904 // https://gist.github.com/37b17df75cc4d7a90bf6
1905 sm2
._wD(fN
+ 'Resuming from paused state', 1);
1910 s
._iO
= mixin(oOptions
, s
._iO
);
1912 // apply from/to parameters, if they exist (and not using RTMP)
1913 if (s
._iO
.from !== null && s
._iO
.to
!== null && s
.instanceCount
=== 0 && s
.playState
=== 0 && !s
._iO
.serverURL
) {
1915 onready = function() {
1916 // sound "canplay" or onload()
1917 // re-apply from/to to instance options, and start playback
1918 s
._iO
= mixin(oOptions
, s
._iO
);
1922 // HTML5 needs to at least have "canplay" fired before seeking.
1923 if (s
.isHTML5
&& !s
._html5_canplay
) {
1925 // this hasn't been loaded yet. load it first, and then do this again.
1926 sm2
._wD(fN
+ 'Beginning load for from/to case');
1929 // note: custom HTML5-only event added for from/to implementation.
1935 } else if (!s
.isHTML5
&& !s
.loaded
&& (!s
.readyState
|| s
.readyState
!== 2)) {
1937 // to be safe, preload the whole thing in Flash.
1939 sm2
._wD(fN
+ 'Preloading for from/to case');
1949 if (exit
!== null) {
1953 // otherwise, we're ready to go. re-apply local options, and continue
1955 s
._iO
= applyFromTo();
1959 // sm2._wD(fN + 'Starting to play');
1961 // increment instance counter, where enabled + supported
1962 if (!s
.instanceCount
|| s
._iO
.multiShotEvents
|| (s
.isHTML5
&& s
._iO
.multiShot
&& !useGlobalHTML5Audio
) || (!s
.isHTML5
&& fV
> 8 && !s
.getAutoPlay())) {
1966 // if first play and onposition parameters exist, apply them now
1967 if (s
._iO
.onposition
&& s
.playState
=== 0) {
1968 attachOnPosition(s
);
1974 s
.position
= (s
._iO
.position
!== _undefined
&& !isNaN(s
._iO
.position
) ? s
._iO
.position : 0);
1977 s
._iO
= policyFix(loopFix(s
._iO
));
1980 if (s
._iO
.onplay
&& _updatePlayState
) {
1981 s
._iO
.onplay
.apply(s
);
1982 onplay_called
= true;
1985 s
.setVolume(s
._iO
.volume
, true);
1986 s
.setPan(s
._iO
.pan
, true);
1990 startOK
= flash
._start(s
.id
, s
._iO
.loops
|| 1, (fV
=== 9 ? s
.position : s
.position
/ msecScale
), s
._iO
.multiShot
|| false);
1992 if (fV
=== 9 && !startOK
) {
1993 // edge case: no sound hardware, or 32-channel flash ceiling hit.
1994 // applies only to Flash 9, non-NetStream/MovieStar sounds.
1995 // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
1996 sm2
._wD(fN
+ 'No sound hardware, or 32-sound ceiling hit', 2);
1997 if (s
._iO
.onplayerror
) {
1998 s
._iO
.onplayerror
.apply(s
);
2005 if (s
.instanceCount
< 2) {
2007 // HTML5 single-instance case
2009 start_html5_timer();
2011 a
= s
._setup_html5();
2013 s
.setPosition(s
._iO
.position
);
2019 // HTML5 multi-shot case
2021 sm2
._wD(s
.id
+ ': Cloning Audio() for instance #' + s
.instanceCount
+ '...');
2023 audioClone
= new Audio(s
._iO
.url
);
2025 onended = function() {
2026 event
.remove(audioClone
, 'ended', onended
);
2029 html5Unload(audioClone
);
2033 oncanplay = function() {
2034 event
.remove(audioClone
, 'canplay', oncanplay
);
2036 audioClone
.currentTime
= s
._iO
.position
/msecScale
;
2038 complain(s
.id
+ ': multiShot play() failed to apply position of ' + (s
._iO
.position
/msecScale
));
2043 event
.add(audioClone
, 'ended', onended
);
2045 // apply volume to clones, too
2046 if (s
._iO
.volume
!== undefined) {
2047 audioClone
.volume
= Math
.max(0, Math
.min(1, s
._iO
.volume
/100));
2050 // playing multiple muted sounds? if you do this, you're weird ;) - but let's cover it.
2052 audioClone
.muted
= true;
2055 if (s
._iO
.position
) {
2056 // HTML5 audio can't seek before onplay() event has fired.
2057 // wait for canplay, then seek to position and start playback.
2058 event
.add(audioClone
, 'canplay', oncanplay
);
2060 // begin playback at currentTime: 0
2074 // just for convenience
2075 this.start
= this.play
;
2078 * Stops playing a sound (and optionally, all sounds)
2080 * @param {boolean} bAll Optional: Whether to stop all sounds
2081 * @return {SMSound} The SMSound object
2084 this.stop = function(bAll
) {
2086 var instanceOptions
= s
._iO
,
2089 if (s
.playState
=== 1) {
2091 sm2
._wD(s
.id
+ ': stop()');
2093 s
._onbufferchange(0);
2094 s
._resetOnPosition(0);
2101 // remove onPosition listeners, if any
2104 // and "to" position, if set
2105 if (instanceOptions
.to
) {
2106 s
.clearOnPosition(instanceOptions
.to
);
2111 flash
._stop(s
.id
, bAll
);
2113 // hack for netStream: just unload
2114 if (instanceOptions
.serverURL
) {
2122 originalPosition
= s
.position
;
2124 // act like Flash, though
2127 // hack: reflect old position for onstop() (also like Flash)
2128 s
.position
= originalPosition
;
2130 // html5 has no stop()
2131 // NOTE: pausing means iOS requires interaction to resume.
2145 s
.instanceCount
= 0;
2148 if (instanceOptions
.onstop
) {
2149 instanceOptions
.onstop
.apply(s
);
2159 * Undocumented/internal: Sets autoPlay for RTMP.
2161 * @param {boolean} autoPlay state
2164 this.setAutoPlay = function(autoPlay
) {
2166 sm2
._wD(s
.id
+ ': Autoplay turned ' + (autoPlay
? 'on' : 'off'));
2167 s
._iO
.autoPlay
= autoPlay
;
2170 flash
._setAutoPlay(s
.id
, autoPlay
);
2172 // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
2173 if (!s
.instanceCount
&& s
.readyState
=== 1) {
2175 sm2
._wD(s
.id
+ ': Incremented instance count to '+s
.instanceCount
);
2183 * Undocumented/internal: Returns the autoPlay boolean.
2185 * @return {boolean} The current autoPlay value
2188 this.getAutoPlay = function() {
2190 return s
._iO
.autoPlay
;
2195 * Sets the position of a sound.
2197 * @param {number} nMsecOffset Position (milliseconds)
2198 * @return {SMSound} The SMSound object
2201 this.setPosition = function(nMsecOffset
) {
2203 if (nMsecOffset
=== _undefined
) {
2207 var position
, position1K
,
2208 // Use the duration from the instance options, if we don't have a track duration yet.
2209 // position >= 0 and <= current available (loaded) duration
2210 offset
= (s
.isHTML5
? Math
.max(nMsecOffset
, 0) : Math
.min(s
.duration
|| s
._iO
.duration
, Math
.max(nMsecOffset
, 0)));
2212 s
.position
= offset
;
2213 position1K
= s
.position
/msecScale
;
2214 s
._resetOnPosition(s
.position
);
2215 s
._iO
.position
= offset
;
2219 position
= (fV
=== 9 ? s
.position : position1K
);
2221 if (s
.readyState
&& s
.readyState
!== 2) {
2222 // if paused or not playing, will not resume (by playing)
2223 flash
._setPosition(s
.id
, position
, (s
.paused
|| !s
.playState
), s
._iO
.multiShot
);
2228 // Set the position in the canplay handler if the sound is not ready yet
2229 if (s
._html5_canplay
) {
2231 if (s
._a
.currentTime
!== position1K
) {
2234 * DOM/JS errors/exceptions to watch out for:
2235 * if seek is beyond (loaded?) position, "DOM exception 11"
2236 * "INDEX_SIZE_ERR": DOM exception 1
2238 sm2
._wD(s
.id
+ ': setPosition('+position1K
+')');
2241 s
._a
.currentTime
= position1K
;
2242 if (s
.playState
=== 0 || s
.paused
) {
2243 // allow seek without auto-play/resume
2247 sm2
._wD(s
.id
+ ': setPosition(' + position1K
+ ') failed: ' + e
.message
, 2);
2252 } else if (position1K
) {
2254 // warn on non-zero seek attempts
2255 sm2
._wD(s
.id
+ ': setPosition(' + position1K
+ '): Cannot seek yet, sound not ready', 2);
2262 // if paused, refresh UI right away
2275 * Pauses sound playback.
2277 * @return {SMSound} The SMSound object
2280 this.pause = function(_bCallFlash
) {
2282 if (s
.paused
|| (s
.playState
=== 0 && s
.readyState
!== 1)) {
2286 sm2
._wD(s
.id
+ ': pause()');
2290 if (_bCallFlash
|| _bCallFlash
=== _undefined
) {
2291 flash
._pause(s
.id
, s
._iO
.multiShot
);
2294 s
._setup_html5().pause();
2298 if (s
._iO
.onpause
) {
2299 s
._iO
.onpause
.apply(s
);
2307 * Resumes sound playback.
2309 * @return {SMSound} The SMSound object
2313 * When auto-loaded streams pause on buffer full they have a playState of 0.
2314 * We need to make sure that the playState is set to 1 when these streams "resume".
2315 * When a paused stream is resumed, we need to trigger the onplay() callback if it
2316 * hasn't been called already. In this case since the sound is being played for the
2317 * first time, I think it's more appropriate to call onplay() rather than onresume().
2320 this.resume = function() {
2322 var instanceOptions
= s
._iO
;
2328 sm2
._wD(s
.id
+ ': resume()');
2333 if (instanceOptions
.isMovieStar
&& !instanceOptions
.serverURL
) {
2334 // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.
2335 s
.setPosition(s
.position
);
2337 // flash method is toggle-based (pause/resume)
2338 flash
._pause(s
.id
, instanceOptions
.multiShot
);
2340 s
._setup_html5().play();
2341 start_html5_timer();
2344 if (!onplay_called
&& instanceOptions
.onplay
) {
2345 instanceOptions
.onplay
.apply(s
);
2346 onplay_called
= true;
2347 } else if (instanceOptions
.onresume
) {
2348 instanceOptions
.onresume
.apply(s
);
2356 * Toggles sound playback.
2358 * @return {SMSound} The SMSound object
2361 this.togglePause = function() {
2363 sm2
._wD(s
.id
+ ': togglePause()');
2365 if (s
.playState
=== 0) {
2367 position: (fV
=== 9 && !s
.isHTML5
? s
.position : s
.position
/ msecScale
)
2383 * Sets the panning (L-R) effect.
2385 * @param {number} nPan The pan value (-100 to 100)
2386 * @return {SMSound} The SMSound object
2389 this.setPan = function(nPan
, bInstanceOnly
) {
2391 if (nPan
=== _undefined
) {
2395 if (bInstanceOnly
=== _undefined
) {
2396 bInstanceOnly
= false;
2400 flash
._setPan(s
.id
, nPan
);
2401 } // else { no HTML5 pan? }
2405 if (!bInstanceOnly
) {
2407 s
.options
.pan
= nPan
;
2417 * @param {number} nVol The volume value (0 to 100)
2418 * @return {SMSound} The SMSound object
2421 this.setVolume = function(nVol
, _bInstanceOnly
) {
2424 * Note: Setting volume has no effect on iOS "special snowflake" devices.
2425 * Hardware volume control overrides software, and volume
2426 * will always return 1 per Apple docs. (iOS 4 + 5.)
2427 * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html
2430 if (nVol
=== _undefined
) {
2434 if (_bInstanceOnly
=== _undefined
) {
2435 _bInstanceOnly
= false;
2439 flash
._setVolume(s
.id
, (sm2
.muted
&& !s
.muted
) || s
.muted
?0:nVol
);
2441 if (sm2
.muted
&& !s
.muted
) {
2446 s
._a
.volume
= Math
.max(0, Math
.min(1, nVol
/100));
2449 s
._iO
.volume
= nVol
;
2451 if (!_bInstanceOnly
) {
2453 s
.options
.volume
= nVol
;
2463 * @return {SMSound} The SMSound object
2466 this.mute = function() {
2471 flash
._setVolume(s
.id
, 0);
2481 * Unmutes the sound.
2483 * @return {SMSound} The SMSound object
2486 this.unmute = function() {
2489 var hasIO
= (s
._iO
.volume
!== _undefined
);
2492 flash
._setVolume(s
.id
, hasIO
?s
._iO
.volume:s
.options
.volume
);
2502 * Toggles the muted state of a sound.
2504 * @return {SMSound} The SMSound object
2507 this.toggleMute = function() {
2509 return (s
.muted
?s
.unmute():s
.mute());
2514 * Registers a callback to be fired when a sound reaches a given position during playback.
2516 * @param {number} nPosition The position to watch for
2517 * @param {function} oMethod The relevant callback to fire
2518 * @param {object} oScope Optional: The scope to apply the callback to
2519 * @return {SMSound} The SMSound object
2522 this.onPosition = function(nPosition
, oMethod
, oScope
) {
2524 // TODO: basic dupe checking?
2526 onPositionItems
.push({
2527 position: parseInt(nPosition
, 10),
2529 scope: (oScope
!== _undefined
? oScope : s
),
2537 // legacy/backwards-compability: lower-case method name
2538 this.onposition
= this.onPosition
;
2541 * Removes registered callback(s) from a sound, by position and/or callback.
2543 * @param {number} nPosition The position to clear callback(s) for
2544 * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position
2545 * @return {SMSound} The SMSound object
2548 this.clearOnPosition = function(nPosition
, oMethod
) {
2552 nPosition
= parseInt(nPosition
, 10);
2554 if (isNaN(nPosition
)) {
2559 for (i
=0; i
< onPositionItems
.length
; i
++) {
2561 if (nPosition
=== onPositionItems
[i
].position
) {
2562 // remove this item if no method was specified, or, if the method matches
2563 if (!oMethod
|| (oMethod
=== onPositionItems
[i
].method
)) {
2564 if (onPositionItems
[i
].fired
) {
2565 // decrement "fired" counter, too
2568 onPositionItems
.splice(i
, 1);
2576 this._processOnPosition = function() {
2578 var i
, item
, j
= onPositionItems
.length
;
2580 if (!j
|| !s
.playState
|| onPositionFired
>= j
) {
2584 for (i
=j
-1; i
>= 0; i
--) {
2585 item
= onPositionItems
[i
];
2586 if (!item
.fired
&& s
.position
>= item
.position
) {
2589 item
.method
.apply(item
.scope
, [item
.position
]);
2590 j
= onPositionItems
.length
; // reset j -- onPositionItems.length can be changed in the item callback above... occasionally breaking the loop.
2598 this._resetOnPosition = function(nPosition
) {
2600 // reset "fired" for items interested in this position
2601 var i
, item
, j
= onPositionItems
.length
;
2607 for (i
=j
-1; i
>= 0; i
--) {
2608 item
= onPositionItems
[i
];
2609 if (item
.fired
&& nPosition
<= item
.position
) {
2620 * SMSound() private internals
2621 * --------------------------------
2624 applyFromTo = function() {
2626 var instanceOptions
= s
._iO
,
2627 f
= instanceOptions
.from,
2628 t
= instanceOptions
.to
,
2633 // end has been reached.
2634 sm2
._wD(s
.id
+ ': "To" time of ' + t
+ ' reached.');
2637 s
.clearOnPosition(t
, end
);
2639 // stop should clear this, too
2644 start = function() {
2646 sm2
._wD(s
.id
+ ': Playing "from" ' + f
);
2648 // add listener for end
2649 if (t
!== null && !isNaN(t
)) {
2650 s
.onPosition(t
, end
);
2655 if (f
!== null && !isNaN(f
)) {
2657 // apply to instance options, guaranteeing correct start position.
2658 instanceOptions
.position
= f
;
2660 // multiShot timing can't be tracked, so prevent that.
2661 instanceOptions
.multiShot
= false;
2667 // return updated instanceOptions including starting position
2668 return instanceOptions
;
2672 attachOnPosition = function() {
2675 op
= s
._iO
.onposition
;
2677 // attach onposition things, if any, now.
2682 if (op
.hasOwnProperty(item
)) {
2683 s
.onPosition(parseInt(item
, 10), op
[item
]);
2691 detachOnPosition = function() {
2694 op
= s
._iO
.onposition
;
2696 // detach any onposition()-style listeners.
2701 if (op
.hasOwnProperty(item
)) {
2702 s
.clearOnPosition(parseInt(item
, 10));
2710 start_html5_timer = function() {
2718 stop_html5_timer = function() {
2726 resetProperties = function(retainPosition
) {
2728 if (!retainPosition
) {
2729 onPositionItems
= [];
2730 onPositionFired
= 0;
2733 onplay_called
= false;
2737 s
._html5_canplay
= false;
2738 s
.bytesLoaded
= null;
2739 s
.bytesTotal
= null;
2740 s
.duration
= (s
._iO
&& s
._iO
.duration
? s
._iO
.duration : null);
2741 s
.durationEstimate
= null;
2748 s
.eqData
.right
= [];
2751 s
.isBuffering
= false;
2752 s
.instanceOptions
= {};
2753 s
.instanceCount
= 0;
2757 // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
2783 * Pseudo-private SMSound internals
2784 * --------------------------------
2787 this._onTimer = function(bForce
) {
2790 * HTML5-only _whileplaying() etc.
2791 * called from both HTML5 native events, and polling/interval-based timers
2792 * mimics flash and fires only when time/duration change, so as to be polling-friendly
2795 var duration
, isNew
= false, time
, x
= {};
2797 if (s
._hasTimer
|| bForce
) {
2799 // TODO: May not need to track readyState (1 = loading)
2801 if (s
._a
&& (bForce
|| ((s
.playState
> 0 || s
.readyState
=== 1) && !s
.paused
))) {
2803 duration
= s
._get_html5_duration();
2805 if (duration
!== lastHTML5State
.duration
) {
2807 lastHTML5State
.duration
= duration
;
2808 s
.duration
= duration
;
2813 // TODO: investigate why this goes wack if not set/re-set each time.
2814 s
.durationEstimate
= s
.duration
;
2816 time
= (s
._a
.currentTime
* msecScale
|| 0);
2818 if (time
!== lastHTML5State
.time
) {
2820 lastHTML5State
.time
= time
;
2825 if (isNew
|| bForce
) {
2827 s
._whileplaying(time
,x
,x
,x
,x
);
2833 // sm2._wD('_onTimer: Warn for "'+s.id+'": '+(!s._a?'Could not find element. ':'')+(s.playState === 0?'playState bad, 0?':'playState = '+s.playState+', OK'));
2845 this._get_html5_duration = function() {
2847 var instanceOptions
= s
._iO
,
2848 // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null
2849 d
= (s
._a
&& s
._a
.duration
? s
._a
.duration
*msecScale : (instanceOptions
&& instanceOptions
.duration
? instanceOptions
.duration : null)),
2850 result
= (d
&& !isNaN(d
) && d
!== Infinity
? d : null);
2856 this._apply_loop = function(a
, nLoops
) {
2859 * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
2860 * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
2864 if (!a
.loop
&& nLoops
> 1) {
2865 sm2
._wD('Note: Native HTML5 looping is infinite.', 1);
2869 a
.loop
= (nLoops
> 1 ? 'loop' : '');
2873 this._setup_html5 = function(oOptions
) {
2875 var instanceOptions
= mixin(s
._iO
, oOptions
),
2876 a
= useGlobalHTML5Audio
? globalHTML5Audio : s
._a
,
2877 dURL
= decodeURI(instanceOptions
.url
),
2881 * "First things first, I, Poppa..." (reset the previous state of the old sound, if playing)
2882 * Fixes case with devices that can only play one sound at a time
2883 * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state
2886 if (useGlobalHTML5Audio
) {
2888 if (dURL
=== decodeURI(lastGlobalHTML5URL
)) {
2889 // global HTML5 audio: re-use of URL
2893 } else if (dURL
=== decodeURI(lastURL
)) {
2895 // options URL is the same as the "last" URL, and we used (loaded) it
2904 if (useGlobalHTML5Audio
) {
2906 if (a
._s
&& a
._s
.playState
&& !sameURL
) {
2908 // global HTML5 audio case, and loading a new URL. stop the currently-playing one.
2913 } else if (!useGlobalHTML5Audio
&& dURL
=== decodeURI(lastURL
)) {
2915 // non-global HTML5 reuse case: same url, ignore request
2916 s
._apply_loop(a
, instanceOptions
.loops
);
2926 // don't retain onPosition() stuff with new URLs.
2929 resetProperties(false);
2932 // assign new HTML5 URL
2934 a
.src
= instanceOptions
.url
;
2936 s
.url
= instanceOptions
.url
;
2938 lastURL
= instanceOptions
.url
;
2940 lastGlobalHTML5URL
= instanceOptions
.url
;
2942 a
._called_load
= false;
2948 if (instanceOptions
.autoLoad
|| instanceOptions
.autoPlay
) {
2950 s
._a
= new Audio(instanceOptions
.url
);
2955 // null for stupid Opera 9.64 case
2956 s
._a
= (isOpera
&& opera
.version() < 10 ? new Audio(null) : new Audio());
2960 // assign local reference
2963 a
._called_load
= false;
2965 if (useGlobalHTML5Audio
) {
2967 globalHTML5Audio
= a
;
2975 // store a ref on the track
2978 // store a ref on the audio
2983 s
._apply_loop(a
, instanceOptions
.loops
);
2985 if (instanceOptions
.autoLoad
|| instanceOptions
.autoPlay
) {
2991 // early HTML5 implementation (non-standard)
2992 a
.autobuffer
= false;
2994 // standard ('none' is also an option.)
3003 add_html5_events = function() {
3005 if (s
._a
._added_events
) {
3011 function add(oEvt
, oFn
, bCapture
) {
3012 return s
._a
? s
._a
.addEventListener(oEvt
, oFn
, bCapture
||false) : null;
3015 s
._a
._added_events
= true;
3017 for (f
in html5_events
) {
3018 if (html5_events
.hasOwnProperty(f
)) {
3019 add(f
, html5_events
[f
]);
3027 remove_html5_events = function() {
3029 // Remove event listeners
3033 function remove(oEvt
, oFn
, bCapture
) {
3034 return (s
._a
? s
._a
.removeEventListener(oEvt
, oFn
, bCapture
||false) : null);
3037 sm2
._wD(s
.id
+ ': Removing event listeners');
3038 s
._a
._added_events
= false;
3040 for (f
in html5_events
) {
3041 if (html5_events
.hasOwnProperty(f
)) {
3042 remove(f
, html5_events
[f
]);
3049 * Pseudo-private event internals
3050 * ------------------------------
3053 this._onload = function(nSuccess
) {
3056 // check for duration to prevent false positives from flash 8 when loading from cache.
3057 loadOK
= !!nSuccess
|| (!s
.isHTML5
&& fV
=== 8 && s
.duration
);
3061 sm2
._wD(fN
+ (loadOK
? 'onload()' : 'Failed to load / invalid sound?' + (!s
.duration
? ' Zero-length duration reported.' : ' -') + ' (' + s
.url
+ ')'), (loadOK
? 1 : 2));
3062 if (!loadOK
&& !s
.isHTML5
) {
3063 if (sm2
.sandbox
.noRemote
=== true) {
3064 sm2
._wD(fN
+ str('noNet'), 1);
3066 if (sm2
.sandbox
.noLocal
=== true) {
3067 sm2
._wD(fN
+ str('noLocal'), 1);
3073 s
.readyState
= loadOK
?3:2;
3074 s
._onbufferchange(0);
3077 wrapCallback(s
, function() {
3078 s
._iO
.onload
.apply(s
, [loadOK
]);
3086 this._onbufferchange = function(nIsBuffering
) {
3088 if (s
.playState
=== 0) {
3089 // ignore if not playing
3093 if ((nIsBuffering
&& s
.isBuffering
) || (!nIsBuffering
&& !s
.isBuffering
)) {
3097 s
.isBuffering
= (nIsBuffering
=== 1);
3098 if (s
._iO
.onbufferchange
) {
3099 sm2
._wD(s
.id
+ ': Buffer state change: ' + nIsBuffering
);
3100 s
._iO
.onbufferchange
.apply(s
);
3108 * Playback may have stopped due to buffering, or related reason.
3109 * This state can be encountered on iOS < 6 when auto-play is blocked.
3112 this._onsuspend = function() {
3114 if (s
._iO
.onsuspend
) {
3115 sm2
._wD(s
.id
+ ': Playback suspended');
3116 s
._iO
.onsuspend
.apply(s
);
3124 * flash 9/movieStar + RTMP-only method, should fire only once at most
3125 * at this point we just recreate failed sounds rather than trying to reconnect
3128 this._onfailure = function(msg
, level
, code
) {
3131 sm2
._wD(s
.id
+ ': Failures = ' + s
.failures
);
3133 if (s
._iO
.onfailure
&& s
.failures
=== 1) {
3134 s
._iO
.onfailure(s
, msg
, level
, code
);
3136 sm2
._wD(s
.id
+ ': Ignoring failure');
3141 this._onfinish = function() {
3143 // store local copy before it gets trashed...
3144 var io_onfinish
= s
._iO
.onfinish
;
3146 s
._onbufferchange(0);
3147 s
._resetOnPosition(0);
3149 // reset some state items
3150 if (s
.instanceCount
) {
3154 if (!s
.instanceCount
) {
3156 // remove onPosition listeners, if any
3159 // reset instance options
3162 s
.instanceCount
= 0;
3163 s
.instanceOptions
= {};
3167 // reset position, too
3174 if (!s
.instanceCount
|| s
._iO
.multiShotEvents
) {
3175 // fire onfinish for last, or every instance
3177 sm2
._wD(s
.id
+ ': onfinish()');
3178 wrapCallback(s
, function() {
3179 io_onfinish
.apply(s
);
3188 this._whileloading = function(nBytesLoaded
, nBytesTotal
, nDuration
, nBufferLength
) {
3190 var instanceOptions
= s
._iO
;
3192 s
.bytesLoaded
= nBytesLoaded
;
3193 s
.bytesTotal
= nBytesTotal
;
3194 s
.duration
= Math
.floor(nDuration
);
3195 s
.bufferLength
= nBufferLength
;
3197 if (!s
.isHTML5
&& !instanceOptions
.isMovieStar
) {
3199 if (instanceOptions
.duration
) {
3200 // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.
3201 s
.durationEstimate
= (s
.duration
> instanceOptions
.duration
) ? s
.duration : instanceOptions
.duration
;
3203 s
.durationEstimate
= parseInt((s
.bytesTotal
/ s
.bytesLoaded
) * s
.duration
, 10);
3208 s
.durationEstimate
= s
.duration
;
3212 // for flash, reflect sequential-load-style buffering
3220 // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
3221 if ((s
.readyState
!== 3 || s
.isHTML5
) && instanceOptions
.whileloading
) {
3222 instanceOptions
.whileloading
.apply(s
);
3227 this._whileplaying = function(nPosition
, oPeakData
, oWaveformDataLeft
, oWaveformDataRight
, oEQData
) {
3229 var instanceOptions
= s
._iO
,
3232 if (isNaN(nPosition
) || nPosition
=== null) {
3237 // 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.
3238 s
.position
= Math
.max(0, nPosition
);
3240 s
._processOnPosition();
3242 if (!s
.isHTML5
&& fV
> 8) {
3244 if (instanceOptions
.usePeakData
&& oPeakData
!== _undefined
&& oPeakData
) {
3246 left: oPeakData
.leftPeak
,
3247 right: oPeakData
.rightPeak
3251 if (instanceOptions
.useWaveformData
&& oWaveformDataLeft
!== _undefined
&& oWaveformDataLeft
) {
3253 left: oWaveformDataLeft
.split(','),
3254 right: oWaveformDataRight
.split(',')
3258 if (instanceOptions
.useEQData
) {
3259 if (oEQData
!== _undefined
&& oEQData
&& oEQData
.leftEQ
) {
3260 eqLeft
= oEQData
.leftEQ
.split(',');
3262 s
.eqData
.left
= eqLeft
;
3263 if (oEQData
.rightEQ
!== _undefined
&& oEQData
.rightEQ
) {
3264 s
.eqData
.right
= oEQData
.rightEQ
.split(',');
3271 if (s
.playState
=== 1) {
3273 // special case/hack: ensure buffering is false if loading from cache (and not yet started)
3274 if (!s
.isHTML5
&& fV
=== 8 && !s
.position
&& s
.isBuffering
) {
3275 s
._onbufferchange(0);
3278 if (instanceOptions
.whileplaying
) {
3279 // flash may call after actual finish
3280 instanceOptions
.whileplaying
.apply(s
);
3289 this._oncaptiondata = function(oData
) {
3292 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
3294 * @param {object} oData
3297 sm2
._wD(s
.id
+ ': Caption data received.');
3299 s
.captiondata
= oData
;
3301 if (s
._iO
.oncaptiondata
) {
3302 s
._iO
.oncaptiondata
.apply(s
, [oData
]);
3307 this._onmetadata = function(oMDProps
, oMDData
) {
3310 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
3311 * RTMP may include song title, MovieStar content may include encoding info
3313 * @param {array} oMDProps (names)
3314 * @param {array} oMDData (values)
3317 sm2
._wD(s
.id
+ ': Metadata received.');
3319 var oData
= {}, i
, j
;
3321 for (i
= 0, j
= oMDProps
.length
; i
< j
; i
++) {
3322 oData
[oMDProps
[i
]] = oMDData
[i
];
3326 if (s
._iO
.onmetadata
) {
3327 s
._iO
.onmetadata
.apply(s
);
3332 this._onid3 = function(oID3Props
, oID3Data
) {
3335 * internal: flash 8 + flash 9 ID3 feature
3336 * may include artist, song title etc.
3338 * @param {array} oID3Props (names)
3339 * @param {array} oID3Data (values)
3342 sm2
._wD(s
.id
+ ': ID3 data received.');
3344 var oData
= [], i
, j
;
3346 for (i
= 0, j
= oID3Props
.length
; i
< j
; i
++) {
3347 oData
[oID3Props
[i
]] = oID3Data
[i
];
3349 s
.id3
= mixin(s
.id3
, oData
);
3352 s
._iO
.onid3
.apply(s
);
3359 this._onconnect = function(bSuccess
) {
3361 bSuccess
= (bSuccess
=== 1);
3362 sm2
._wD(s
.id
+ ': ' + (bSuccess
? 'Connected.' : 'Failed to connect? - ' + s
.url
), (bSuccess
? 1 : 2));
3363 s
.connected
= bSuccess
;
3369 if (idCheck(s
.id
)) {
3370 if (s
.getAutoPlay()) {
3371 // only update the play state if auto playing
3372 s
.play(_undefined
, s
.getAutoPlay());
3373 } else if (s
._iO
.autoLoad
) {
3378 if (s
._iO
.onconnect
) {
3379 s
._iO
.onconnect
.apply(s
, [bSuccess
]);
3386 this._ondataerror = function(sError
) {
3388 // flash 9 wave/eq data handler
3389 // hack: called at start, and end from flash at/after onfinish()
3390 if (s
.playState
> 0) {
3391 sm2
._wD(s
.id
+ ': Data error: ' + sError
);
3392 if (s
._iO
.ondataerror
) {
3393 s
._iO
.ondataerror
.apply(s
);
3406 * Private SoundManager internals
3407 * ------------------------------
3410 getDocument = function() {
3412 return (doc
.body
|| doc
.getElementsByTagName('div')[0]);
3416 id = function(sID
) {
3418 return doc
.getElementById(sID
);
3422 mixin = function(oMain
, oAdd
) {
3424 // non-destructive merge
3425 var o1
= (oMain
|| {}), o2
, o
;
3427 // if unspecified, o2 is the default options object
3428 o2
= (oAdd
=== _undefined
? sm2
.defaultOptions : oAdd
);
3432 if (o2
.hasOwnProperty(o
) && o1
[o
] === _undefined
) {
3434 if (typeof o2
[o
] !== 'object' || o2
[o
] === null) {
3441 // recurse through o2
3442 o1
[o
] = mixin(o1
[o
], o2
[o
]);
3454 wrapCallback = function(oSound
, callback
) {
3457 * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue
3458 * 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).
3459 * 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.
3460 * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.
3461 * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player
3463 if (!oSound
.isHTML5
&& fV
=== 8) {
3464 window
.setTimeout(callback
, 0);
3471 // additional soundManager properties that soundManager.setup() will accept
3476 'defaultOptions': 1,
3478 'movieStarOptions': 1
3481 assign = function(o
, oParent
) {
3484 * recursive assignment of properties, soundManager.setup() helper
3485 * allows property assignment based on whitelist
3490 hasParent
= (oParent
!== _undefined
),
3491 setupOptions
= sm2
.setupOptions
,
3492 bonusOptions
= extraOptions
;
3496 // if soundManager.setup() called, show accepted parameters.
3498 if (o
=== _undefined
) {
3502 for (i
in setupOptions
) {
3504 if (setupOptions
.hasOwnProperty(i
)) {
3510 for (i
in bonusOptions
) {
3512 if (bonusOptions
.hasOwnProperty(i
)) {
3514 if (typeof sm2
[i
] === 'object') {
3516 result
.push(i
+': {...}');
3518 } else if (sm2
[i
] instanceof Function
) {
3520 result
.push(i
+': function() {...}');
3532 sm2
._wD(str('setup', result
.join(', ')));
3542 if (o
.hasOwnProperty(i
)) {
3544 // if not an {object} we want to recurse through...
3546 if (typeof o
[i
] !== 'object' || o
[i
] === null || o
[i
] instanceof Array
|| o
[i
] instanceof RegExp
) {
3548 // check "allowed" options
3550 if (hasParent
&& bonusOptions
[oParent
] !== _undefined
) {
3552 // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
3553 sm2
[oParent
][i
] = o
[i
];
3555 } else if (setupOptions
[i
] !== _undefined
) {
3557 // special case: assign to setupOptions object, which soundManager property references
3558 sm2
.setupOptions
[i
] = o
[i
];
3560 // assign directly to soundManager, too
3563 } else if (bonusOptions
[i
] === _undefined
) {
3565 // invalid or disallowed parameter. complain.
3566 complain(str((sm2
[i
] === _undefined
? 'setupUndef' : 'setupError'), i
), 2);
3573 * valid extraOptions (bonusOptions) parameter.
3574 * is it a method, like onready/ontimeout? call it.
3575 * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
3578 if (sm2
[i
] instanceof Function
) {
3580 sm2
[i
].apply(sm2
, (o
[i
] instanceof Array
? o
[i
] : [o
[i
]]));
3584 // good old-fashioned direct assignment
3593 // recursion case, eg., { defaultOptions: { ... } }
3595 if (bonusOptions
[i
] === _undefined
) {
3597 // invalid or disallowed parameter. complain.
3598 complain(str((sm2
[i
] === _undefined
? 'setupUndef' : 'setupError'), i
), 2);
3604 // recurse through object
3605 return assign(o
[i
], i
);
3619 function preferFlashCheck(kind
) {
3621 // whether flash should play a given type
3622 return (sm2
.preferFlash
&& hasFlash
&& !sm2
.ignoreFlash
&& (sm2
.flash
[kind
] !== _undefined
&& sm2
.flash
[kind
]));
3627 * Internal DOM2-level event helpers
3628 * ---------------------------------
3631 event
= (function() {
3633 // normalize event methods
3634 var old
= (window
.attachEvent
),
3636 add: (old
?'attachEvent':'addEventListener'),
3637 remove: (old
?'detachEvent':'removeEventListener')
3640 // normalize "on" event prefix, optional capture argument
3641 function getArgs(oArgs
) {
3643 var args
= slice
.call(oArgs
),
3648 args
[1] = 'on' + args
[1];
3653 } else if (len
=== 3) {
3661 function apply(args
, sType
) {
3663 // normalize and call the event method, with the proper arguments
3664 var element
= args
.shift(),
3665 method
= [evt
[sType
]];
3668 // old IE can't do apply().
3669 element
[method
](args
[0], args
[1]);
3671 element
[method
].apply(element
, args
);
3678 apply(getArgs(arguments
), 'add');
3684 apply(getArgs(arguments
), 'remove');
3696 * Internal HTML5 event handling
3697 * -----------------------------
3700 function html5_event(oFn
) {
3702 // wrap html5 event handlers so we don't call them on destroyed and/or unloaded sounds
3704 return function(e
) {
3712 sm2
._wD(s
.id
+ ': Ignoring ' + e
.type
);
3714 sm2
._wD(h5
+ 'Ignoring ' + e
.type
);
3719 result
= oFn
.call(this, e
);
3730 // HTML5 event-name-to-handler map
3732 abort: html5_event(function() {
3734 sm2
._wD(this._s
.id
+ ': abort');
3738 // enough has loaded to play
3740 canplay: html5_event(function() {
3745 if (s
._html5_canplay
) {
3746 // this event has already fired. ignore.
3750 s
._html5_canplay
= true;
3751 sm2
._wD(s
.id
+ ': canplay');
3752 s
._onbufferchange(0);
3754 // position according to instance options
3755 position1K
= (s
._iO
.position
!== _undefined
&& !isNaN(s
._iO
.position
)?s
._iO
.position
/msecScale:null);
3757 // set the position if position was set before the sound loaded
3758 if (s
.position
&& this.currentTime
!== position1K
) {
3759 sm2
._wD(s
.id
+ ': canplay: Setting position to ' + position1K
);
3761 this.currentTime
= position1K
;
3763 sm2
._wD(s
.id
+ ': canplay: Setting position of ' + position1K
+ ' failed: ' + ee
.message
, 2);
3767 // hack for HTML5 from/to case
3768 if (s
._iO
._oncanplay
) {
3774 canplaythrough: html5_event(function() {
3779 s
._onbufferchange(0);
3780 s
._whileloading(s
.bytesLoaded
, s
.bytesTotal
, s
._get_html5_duration());
3786 // TODO: Reserved for potential use
3788 emptied: html5_event(function() {
3790 sm2._wD(this._s.id + ': emptied');
3795 ended: html5_event(function() {
3799 sm2
._wD(s
.id
+ ': ended');
3805 error: html5_event(function() {
3807 sm2
._wD(this._s
.id
+ ': HTML5 error, code ' + this.error
.code
);
3809 * HTML5 error codes, per W3C
3810 * Error 1: Client aborted download at user's request.
3811 * Error 2: Network error after load started.
3812 * Error 3: Decoding issue.
3813 * Error 4: Media (audio file) not supported.
3814 * Reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes
3816 // call load with error state?
3817 this._s
._onload(false);
3821 loadeddata: html5_event(function() {
3825 sm2
._wD(s
.id
+ ': loadeddata');
3827 // safari seems to nicely report progress events, eventually totalling 100%
3828 if (!s
._loaded
&& !isSafari
) {
3829 s
.duration
= s
._get_html5_duration();
3834 loadedmetadata: html5_event(function() {
3836 sm2
._wD(this._s
.id
+ ': loadedmetadata');
3840 loadstart: html5_event(function() {
3842 sm2
._wD(this._s
.id
+ ': loadstart');
3843 // assume buffering at first
3844 this._s
._onbufferchange(1);
3848 play: html5_event(function() {
3850 // sm2._wD(this._s.id + ': play()');
3851 // once play starts, no buffering
3852 this._s
._onbufferchange(0);
3856 playing: html5_event(function() {
3858 sm2
._wD(this._s
.id
+ ': playing');
3859 // once play starts, no buffering
3860 this._s
._onbufferchange(0);
3864 progress: html5_event(function(e
) {
3866 // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
3869 i
, j
, progStr
, buffered
= 0,
3870 isProgress
= (e
.type
=== 'progress'),
3871 ranges
= e
.target
.buffered
,
3872 // firefox 3.6 implements e.loaded/total (bytes)
3873 loaded
= (e
.loaded
||0),
3874 total
= (e
.total
||1);
3876 // reset the "buffered" (loaded byte ranges) array
3879 if (ranges
&& ranges
.length
) {
3881 // if loaded is 0, try TimeRanges implementation as % of load
3882 // https://developer.mozilla.org/en/DOM/TimeRanges
3884 // re-build "buffered" array
3885 // HTML5 returns seconds. SM2 API uses msec for setPosition() etc., whether Flash or HTML5.
3886 for (i
=0, j
=ranges
.length
; i
<j
; i
++) {
3888 'start': ranges
.start(i
) * msecScale
,
3889 'end': ranges
.end(i
) * msecScale
3893 // use the last value locally
3894 buffered
= (ranges
.end(0) - ranges
.start(0)) * msecScale
;
3896 // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
3897 loaded
= Math
.min(1, buffered
/(e
.target
.duration
*msecScale
));
3900 if (isProgress
&& ranges
.length
> 1) {
3903 for (i
=0; i
<j
; i
++) {
3904 progStr
.push(e
.target
.buffered
.start(i
)*msecScale
+'-'+ e
.target
.buffered
.end(i
)*msecScale
);
3906 sm2
._wD(this._s
.id
+ ': progress, timeRanges: ' + progStr
.join(', '));
3909 if (isProgress
&& !isNaN(loaded
)) {
3910 sm2
._wD(this._s
.id
+ ': progress, ' + Math
.floor(loaded
*100) + '% loaded');
3916 if (!isNaN(loaded
)) {
3918 // if progress, likely not buffering
3919 s
._onbufferchange(0);
3920 // TODO: prevent calls with duplicate values.
3921 s
._whileloading(loaded
, total
, s
._get_html5_duration());
3922 if (loaded
&& total
&& loaded
=== total
) {
3923 // in case "onload" doesn't fire (eg. gecko 1.9.2)
3924 html5_events
.canplaythrough
.call(this, e
);
3931 ratechange: html5_event(function() {
3933 sm2
._wD(this._s
.id
+ ': ratechange');
3937 suspend: html5_event(function(e
) {
3939 // download paused/stopped, may have finished (eg. onload)
3942 sm2
._wD(this._s
.id
+ ': suspend');
3943 html5_events
.progress
.call(this, e
);
3948 stalled: html5_event(function() {
3950 sm2
._wD(this._s
.id
+ ': stalled');
3954 timeupdate: html5_event(function() {
3960 waiting: html5_event(function() {
3964 // see also: seeking
3965 sm2
._wD(this._s
.id
+ ': waiting');
3967 // playback faster than download rate, etc.
3968 s
._onbufferchange(1);
3974 html5OK = function(iO
) {
3976 // playability test based on URL or MIME type
3980 if (!iO
|| (!iO
.type
&& !iO
.url
&& !iO
.serverURL
)) {
3985 } else if (iO
.serverURL
|| (iO
.type
&& preferFlashCheck(iO
.type
))) {
3987 // RTMP, or preferring flash
3992 // Use type, if specified. Pass data: URIs to HTML5. If HTML5-only mode, no other options, so just give 'er
3993 result
= ((iO
.type
? html5CanPlay({type:iO
.type
}) : html5CanPlay({url:iO
.url
}) || sm2
.html5Only
|| iO
.url
.match(/data\:/i)));
4001 html5Unload = function(oAudio
) {
4004 * Internal method: Unload media, and cancel any current/pending network requests.
4005 * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
4006 * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
4007 * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
4008 * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
4015 // Firefox and Chrome accept short WAVe data: URIs. Chome dislikes audio/wav, but accepts audio/wav for data: MIME.
4016 // Desktop Safari complains / fails on data: URI, so it gets about:blank.
4017 url
= (isSafari
? emptyURL : (sm2
.html5
.canPlayType('audio/wav') ? emptyWAV : emptyURL
));
4021 // reset some state, too
4022 if (oAudio
._called_unload
!== undefined) {
4023 oAudio
._called_load
= false;
4028 if (useGlobalHTML5Audio
) {
4030 // ensure URL state is trashed, also
4031 lastGlobalHTML5URL
= null;
4039 html5CanPlay = function(o
) {
4042 * Try to find MIME, test and return truthiness
4044 * url: '/path/to/an.mp3',
4049 if (!sm2
.useHTML5Audio
|| !sm2
.hasHTML5
) {
4053 var url
= (o
.url
|| null),
4054 mime
= (o
.type
|| null),
4055 aF
= sm2
.audioFormats
,
4061 // account for known cases like audio/mp3
4063 if (mime
&& sm2
.html5
[mime
] !== _undefined
) {
4064 return (sm2
.html5
[mime
] && !preferFlashCheck(mime
));
4070 if (aF
.hasOwnProperty(item
)) {
4071 html5Ext
.push(item
);
4072 if (aF
[item
].related
) {
4073 html5Ext
= html5Ext
.concat(aF
[item
].related
);
4077 html5Ext
= new RegExp('\\.('+html5Ext
.join('|')+')(\\?.*)?$','i');
4080 // TODO: Strip URL queries, etc.
4081 fileExt
= (url
? url
.toLowerCase().match(html5Ext
) : null);
4083 if (!fileExt
|| !fileExt
.length
) {
4087 // audio/mp3 -> mp3, result should be known
4088 offset
= mime
.indexOf(';');
4089 // strip "audio/X; codecs..."
4090 fileExt
= (offset
!== -1?mime
.substr(0,offset
):mime
).substr(6);
4093 // match the raw extension name - "mp3", for example
4094 fileExt
= fileExt
[1];
4097 if (fileExt
&& sm2
.html5
[fileExt
] !== _undefined
) {
4099 result
= (sm2
.html5
[fileExt
] && !preferFlashCheck(fileExt
));
4101 mime
= 'audio/'+fileExt
;
4102 result
= sm2
.html5
.canPlayType({type:mime
});
4103 sm2
.html5
[fileExt
] = result
;
4104 // sm2._wD('canPlayType, found result: ' + result);
4105 result
= (result
&& sm2
.html5
[mime
] && !preferFlashCheck(mime
));
4112 testHTML5 = function() {
4115 * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on
4116 * assigns results to html5[] and flash[].
4119 if (!sm2
.useHTML5Audio
|| !sm2
.hasHTML5
) {
4120 // without HTML5, we need Flash.
4121 sm2
.html5
.usingFlash
= true;
4126 // 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. :/
4127 var a
= (Audio
!== _undefined
? (isOpera
&& opera
.version() < 10 ? new Audio(null) : new Audio()) : null),
4128 item
, lookup
, support
= {}, aF
, i
;
4136 if (!a
|| typeof a
.canPlayType
!== 'function') {
4140 if (m
instanceof Array
) {
4141 // iterate through all mime types, return any successes
4142 for (i
=0, j
=m
.length
; i
<j
; i
++) {
4143 if (sm2
.html5
[m
[i
]] || a
.canPlayType(m
[i
]).match(sm2
.html5Test
)) {
4145 sm2
.html5
[m
[i
]] = true;
4146 // note flash support, too
4147 sm2
.flash
[m
[i
]] = !!(m
[i
].match(flashMIME
));
4152 canPlay
= (a
&& typeof a
.canPlayType
=== 'function' ? a
.canPlayType(m
) : false);
4153 result
= !!(canPlay
&& (canPlay
.match(sm2
.html5Test
)));
4160 // test all registered formats + codecs
4162 aF
= sm2
.audioFormats
;
4166 if (aF
.hasOwnProperty(item
)) {
4168 lookup
= 'audio/' + item
;
4170 support
[item
] = cp(aF
[item
].type
);
4172 // write back generic type too, eg. audio/mp3
4173 support
[lookup
] = support
[item
];
4176 if (item
.match(flashMIME
)) {
4178 sm2
.flash
[item
] = true;
4179 sm2
.flash
[lookup
] = true;
4183 sm2
.flash
[item
] = false;
4184 sm2
.flash
[lookup
] = false;
4188 // assign result to related formats, too
4190 if (aF
[item
] && aF
[item
].related
) {
4192 for (i
=aF
[item
].related
.length
-1; i
>= 0; i
--) {
4195 support
['audio/'+aF
[item
].related
[i
]] = support
[item
];
4196 sm2
.html5
[aF
[item
].related
[i
]] = support
[item
];
4197 sm2
.flash
[aF
[item
].related
[i
]] = support
[item
];
4207 support
.canPlayType
= (a
?cp:null);
4208 sm2
.html5
= mixin(sm2
.html5
, support
);
4210 sm2
.html5
.usingFlash
= featureCheck();
4211 needsFlash
= sm2
.html5
.usingFlash
;
4220 notReady: 'Unavailable - wait until onready() has fired.',
4221 notOK: 'Audio support is not available.',
4222 domError: sm
+ 'exception caught while appending SWF to DOM.',
4223 spcWmode: 'Removing wmode, preventing known SWF loading issue(s)',
4224 swf404: smc
+ 'Verify that %s is a valid path.',
4225 tryDebug: 'Try ' + sm
+ '.debugFlash = true for more security details (output goes to SWF.)',
4226 checkSWF: 'See SWF output for more debug info.',
4227 localFail: smc
+ '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/',
4228 waitFocus: smc
+ 'Special case: Waiting for SWF to load with window focus...',
4229 waitForever: smc
+ 'Waiting indefinitely for Flash (will recover if unblocked)...',
4230 waitSWF: smc
+ 'Waiting for 100% SWF load...',
4231 needFunction: smc
+ 'Function object expected for %s',
4232 badID: 'Sound ID "%s" should be a string, starting with a non-numeric character',
4233 currentObj: smc
+ '_debug(): Current sound objects',
4234 waitOnload: smc
+ 'Waiting for window.onload()',
4235 docLoaded: smc
+ 'Document already loaded',
4236 onload: smc
+ 'initComplete(): calling soundManager.onload()',
4237 onloadOK: sm
+ '.onload() complete',
4238 didInit: smc
+ 'init(): Already called?',
4239 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',
4240 badRemove: smc
+ 'Failed to remove Flash node.',
4241 shutdown: sm
+ '.disable(): Shutting down',
4242 queue: smc
+ 'Queueing %s handler',
4243 smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
4244 fbTimeout: 'No flash response, applying .'+swfCSS
.swfTimedout
+' CSS...',
4245 fbLoaded: 'Flash loaded',
4246 fbHandler: smc
+ 'flashBlockHandler()',
4247 manURL: 'SMSound.load(): Using manually-assigned URL',
4248 onURL: sm
+ '.load(): current URL already assigned.',
4249 badFV: sm
+ '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',
4250 as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',
4251 noNSLoop: 'Note: Looping not implemented for MovieStar formats',
4252 needfl9: 'Note: Switching to flash 9, required for MP4 formats.',
4253 mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
4254 needFlash: smc
+ 'Fatal error: Flash is needed to play some required formats, but is not available.',
4255 gotFocus: smc
+ 'Got window focus.',
4256 policy: 'Enabling usePolicyFile for data access',
4257 setup: sm
+ '.setup(): allowed parameters: %s',
4258 setupError: sm
+ '.setup(): "%s" cannot be assigned with this method.',
4259 setupUndef: sm
+ '.setup(): Could not find option "%s"',
4260 setupLate: sm
+ '.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().',
4261 noURL: smc
+ 'Flash URL required. Call soundManager.setup({url:...}) to get started.',
4262 sm2Loaded: 'SoundManager 2: Ready.',
4263 reset: sm
+ '.reset(): Removing event callbacks',
4264 mobileUA: 'Mobile UA detected, preferring HTML5 by default.',
4265 globalHTML5: 'Using singleton HTML5 Audio() pattern for this device.'
4272 // internal string replace helper.
4273 // arguments: o [,items to replace]
4280 // real array, please
4281 args
= slice
.call(arguments
);
4286 sstr
= (strings
&& strings
[o
] ? strings
[o
] : '');
4288 if (sstr
&& args
&& args
.length
) {
4289 for (i
= 0, j
= args
.length
; i
< j
; i
++) {
4290 sstr
= sstr
.replace('%s', args
[i
]);
4299 loopFix = function(sOpt
) {
4301 // flash 8 requires stream = false for looping to work
4302 if (fV
=== 8 && sOpt
.loops
> 1 && sOpt
.stream
) {
4304 sOpt
.stream
= false;
4311 policyFix = function(sOpt
, sPre
) {
4313 if (sOpt
&& !sOpt
.usePolicyFile
&& (sOpt
.onid3
|| sOpt
.usePeakData
|| sOpt
.useWaveformData
|| sOpt
.useEQData
)) {
4314 sm2
._wD((sPre
|| '') + str('policy'));
4315 sOpt
.usePolicyFile
= true;
4322 complain = function(sMsg
) {
4325 if (hasConsole
&& console
.warn
!== _undefined
) {
4334 doNothing = function() {
4340 disableObject = function(o
) {
4345 if (o
.hasOwnProperty(oProp
) && typeof o
[oProp
] === 'function') {
4346 o
[oProp
] = doNothing
;
4354 failSafely = function(bNoDisable
) {
4356 // general failure exception handler
4358 if (bNoDisable
=== _undefined
) {
4362 if (disabled
|| bNoDisable
) {
4363 sm2
.disable(bNoDisable
);
4368 normalizeMovieURL = function(smURL
) {
4370 var urlParams
= null, url
;
4373 if (smURL
.match(/\.swf(\?.*)?$/i)) {
4374 urlParams
= smURL
.substr(smURL
.toLowerCase().lastIndexOf('.swf?') + 4);
4376 // assume user knows what they're doing
4379 } else if (smURL
.lastIndexOf('/') !== smURL
.length
- 1) {
4380 // append trailing slash, if needed
4385 url
= (smURL
&& smURL
.lastIndexOf('/') !== - 1 ? smURL
.substr(0, smURL
.lastIndexOf('/') + 1) : './') + sm2
.movieURL
;
4387 if (sm2
.noSWFCache
) {
4388 url
+= ('?ts=' + new Date().getTime());
4395 setVersionInfo = function() {
4397 // short-hand for internal use
4399 fV
= parseInt(sm2
.flashVersion
, 10);
4401 if (fV
!== 8 && fV
!== 9) {
4402 sm2
._wD(str('badFV', fV
, defaultFlashVersion
));
4403 sm2
.flashVersion
= fV
= defaultFlashVersion
;
4406 // debug flash movie, if applicable
4408 var isDebug
= (sm2
.debugMode
|| sm2
.debugFlash
?'_debug.swf':'.swf');
4410 if (sm2
.useHTML5Audio
&& !sm2
.html5Only
&& sm2
.audioFormats
.mp4
.required
&& fV
< 9) {
4411 sm2
._wD(str('needfl9'));
4412 sm2
.flashVersion
= fV
= 9;
4415 sm2
.version
= sm2
.versionNumber
+ (sm2
.html5Only
?' (HTML5-only mode)':(fV
=== 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));
4417 // set up default options
4419 // +flash 9 base options
4420 sm2
.defaultOptions
= mixin(sm2
.defaultOptions
, sm2
.flash9Options
);
4421 sm2
.features
.buffering
= true;
4422 // +moviestar support
4423 sm2
.defaultOptions
= mixin(sm2
.defaultOptions
, sm2
.movieStarOptions
);
4424 sm2
.filePatterns
.flash9
= new RegExp('\\.(mp3|' + netStreamTypes
.join('|') + ')(\\?.*)?$', 'i');
4425 sm2
.features
.movieStar
= true;
4427 sm2
.features
.movieStar
= false;
4430 // regExp for flash canPlay(), etc.
4431 sm2
.filePattern
= sm2
.filePatterns
[(fV
!== 8?'flash9':'flash8')];
4433 // if applicable, use _debug versions of SWFs
4434 sm2
.movieURL
= (fV
=== 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug
);
4436 sm2
.features
.peakData
= sm2
.features
.waveformData
= sm2
.features
.eqData
= (fV
> 8);
4440 setPolling = function(bPolling
, bHighPerformance
) {
4446 flash
._setPolling(bPolling
, bHighPerformance
);
4450 initDebug = function() {
4452 // starts debug mode, creating output <div> for UAs without console object
4454 // allow force of debug mode via URL
4456 if (sm2
.debugURLParam
.test(wl
)) {
4457 sm2
.debugMode
= true;
4460 if (id(sm2
.debugID
)) {
4464 var oD
, oDebug
, oTarget
, oToggle
, tmp
;
4466 if (sm2
.debugMode
&& !id(sm2
.debugID
) && (!hasConsole
|| !sm2
.useConsole
|| !sm2
.consoleOnly
)) {
4468 oD
= doc
.createElement('div');
4469 oD
.id
= sm2
.debugID
+ '-toggle';
4472 'position': 'fixed',
4477 'lineHeight': '1.2em',
4479 'textAlign': 'center',
4480 'border': '1px solid #999',
4481 'cursor': 'pointer',
4482 'background': '#fff',
4487 oD
.appendChild(doc
.createTextNode('-'));
4488 oD
.onclick
= toggleDebug
;
4489 oD
.title
= 'Toggle SM2 debug console';
4491 if (ua
.match(/msie
6/i
)) {
4492 oD
.style
.position
= 'absolute';
4493 oD
.style
.cursor
= 'hand';
4496 for (tmp
in oToggle
) {
4497 if (oToggle
.hasOwnProperty(tmp
)) {
4498 oD
.style
[tmp
] = oToggle
[tmp
];
4502 oDebug
= doc
.createElement('div');
4503 oDebug
.id
= sm2
.debugID
;
4504 oDebug
.style
.display
= (sm2
.debugMode
?'block':'none');
4506 if (sm2
.debugMode
&& !id(oD
.id
)) {
4508 oTarget
= getDocument();
4509 oTarget
.appendChild(oD
);
4511 throw new Error(str('domError')+' \n'+e2
.toString());
4513 oTarget
.appendChild(oDebug
);
4523 idCheck
= this.getSoundById
;
4526 _wDS = function(o
, errorLevel
) {
4528 return (!o
? '' : sm2
._wD(str(o
), errorLevel
));
4532 toggleDebug = function() {
4534 var o
= id(sm2
.debugID
),
4535 oT
= id(sm2
.debugID
+ '-toggle');
4544 o
.style
.display
= 'none';
4547 o
.style
.display
= 'block';
4550 debugOpen
= !debugOpen
;
4554 debugTS = function(sEventType
, bSuccess
, sMessage
) {
4556 // troubleshooter debug hooks
4558 if (window
.sm2Debugger
!== _undefined
) {
4560 sm2Debugger
.handleEvent(sEventType
, bSuccess
, sMessage
);
4572 getSWFCSS = function() {
4576 if (sm2
.debugMode
) {
4577 css
.push(swfCSS
.sm2Debug
);
4580 if (sm2
.debugFlash
) {
4581 css
.push(swfCSS
.flashDebug
);
4584 if (sm2
.useHighPerformance
) {
4585 css
.push(swfCSS
.highPerf
);
4588 return css
.join(' ');
4592 flashBlockHandler = function() {
4594 // *possible* flash block situation.
4596 var name
= str('fbHandler'),
4597 p
= sm2
.getMoviePercent(),
4599 error
= {type:'FLASHBLOCK'};
4601 if (sm2
.html5Only
) {
4602 // no flash, or unused
4609 // make the movie more visible, so user can fix
4610 sm2
.oMC
.className
= getSWFCSS() + ' ' + css
.swfDefault
+ ' ' + (p
=== null?css
.swfTimedout:css
.swfError
);
4611 sm2
._wD(name
+ ': ' + str('fbTimeout') + (p
? ' (' + str('fbLoaded') + ')' : ''));
4614 sm2
.didFlashBlock
= true;
4616 // fire onready(), complain lightly
4617 processOnEvents({type:'ontimeout', ignoreInit:true, error:error
});
4622 // SM2 loaded OK (or recovered)
4625 if (sm2
.didFlashBlock
) {
4626 sm2
._wD(name
+ ': Unblocked');
4631 sm2
.oMC
.className
= [getSWFCSS(), css
.swfDefault
, css
.swfLoaded
+ (sm2
.didFlashBlock
?' '+css
.swfUnblocked:'')].join(' ');
4638 addOnEvent = function(sType
, oMethod
, oScope
) {
4640 if (on_queue
[sType
] === _undefined
) {
4641 on_queue
[sType
] = [];
4644 on_queue
[sType
].push({
4646 'scope': (oScope
|| null),
4652 processOnEvents = function(oOptions
) {
4654 // if unspecified, assume OK/error
4658 type: (sm2
.ok() ? 'onready' : 'ontimeout')
4662 if (!didInit
&& oOptions
&& !oOptions
.ignoreInit
) {
4667 if (oOptions
.type
=== 'ontimeout' && (sm2
.ok() || (disabled
&& !oOptions
.ignoreInit
))) {
4673 success: (oOptions
&& oOptions
.ignoreInit
?sm2
.ok():!disabled
)
4676 // queue specified by type, or none
4677 srcQueue
= (oOptions
&& oOptions
.type
?on_queue
[oOptions
.type
]||[]:[]),
4681 canRetry
= (needsFlash
&& !sm2
.ok());
4683 if (oOptions
.error
) {
4684 args
[0].error
= oOptions
.error
;
4687 for (i
= 0, j
= srcQueue
.length
; i
< j
; i
++) {
4688 if (srcQueue
[i
].fired
!== true) {
4689 queue
.push(srcQueue
[i
]);
4694 // sm2._wD(sm + ': Firing ' + queue.length + ' ' + oOptions.type + '() item' + (queue.length === 1 ? '' : 's'));
4695 for (i
= 0, j
= queue
.length
; i
< j
; i
++) {
4696 if (queue
[i
].scope
) {
4697 queue
[i
].method
.apply(queue
[i
].scope
, args
);
4699 queue
[i
].method
.apply(this, args
);
4702 // useFlashBlock and SWF timeout case doesn't count here.
4703 queue
[i
].fired
= true;
4712 initUserOnload = function() {
4714 window
.setTimeout(function() {
4716 if (sm2
.useFlashBlock
) {
4717 flashBlockHandler();
4722 // call user-defined "onload", scoped to window
4724 if (typeof sm2
.onload
=== 'function') {
4726 sm2
.onload
.apply(window
);
4727 _wDS('onloadOK', 1);
4730 if (sm2
.waitForWindowLoad
) {
4731 event
.add(window
, 'load', initUserOnload
);
4738 detectFlash = function() {
4740 // 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
4742 if (hasFlash
!== _undefined
) {
4743 // this work has already been done.
4747 var hasPlugin
= false, n
= navigator
, nP
= n
.plugins
, obj
, type
, types
, AX
= window
.ActiveXObject
;
4749 if (nP
&& nP
.length
) {
4750 type
= 'application/x-shockwave-flash';
4751 types
= n
.mimeTypes
;
4752 if (types
&& types
[type
] && types
[type
].enabledPlugin
&& types
[type
].enabledPlugin
.description
) {
4755 } else if (AX
!== _undefined
&& !ua
.match(/MSAppHost/i)) {
4756 // Windows 8 Store Apps (MSAppHost) are weird (compatibility?) and won't complain here, but will barf if Flash/ActiveX object is appended to the DOM.
4758 obj
= new AX('ShockwaveFlash.ShockwaveFlash');
4763 hasPlugin
= (!!obj
);
4764 // cleanup, because it is ActiveX after all
4768 hasFlash
= hasPlugin
;
4774 featureCheck = function() {
4778 formats
= sm2
.audioFormats
,
4779 // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
4780 isSpecial
= (is_iDevice
&& !!(ua
.match(/os (1|2|3_0
|3_1
)/i
)));
4784 // has Audio(), but is broken; let it load links directly.
4785 sm2
.hasHTML5
= false;
4787 // ignore flash case, however
4788 sm2
.html5Only
= true;
4790 // hide the SWF, if present
4792 sm2
.oMC
.style
.display
= 'none';
4797 if (sm2
.useHTML5Audio
) {
4799 if (!sm2
.html5
|| !sm2
.html5
.canPlayType
) {
4800 sm2
._wD('SoundManager: No HTML5 Audio() support detected.');
4801 sm2
.hasHTML5
= false;
4806 sm2
._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);
4814 if (sm2
.useHTML5Audio
&& sm2
.hasHTML5
) {
4816 // sort out whether flash is optional, required or can be ignored.
4818 // innocent until proven guilty.
4819 canIgnoreFlash
= true;
4821 for (item
in formats
) {
4822 if (formats
.hasOwnProperty(item
)) {
4823 if (formats
[item
].required
) {
4824 if (!sm2
.html5
.canPlayType(formats
[item
].type
)) {
4825 // 100% HTML5 mode is not possible.
4826 canIgnoreFlash
= false;
4828 } else if (sm2
.preferFlash
&& (sm2
.flash
[item
] || sm2
.flash
[formats
[item
].type
])) {
4829 // flash may be required, or preferred for this format.
4839 if (sm2
.ignoreFlash
) {
4840 flashNeeded
= false;
4841 canIgnoreFlash
= true;
4844 sm2
.html5Only
= (sm2
.hasHTML5
&& sm2
.useHTML5Audio
&& !flashNeeded
);
4846 return (!sm2
.html5Only
);
4850 parseURL = function(url
) {
4853 * Internal: Finds and returns the first playable URL (or failing that, the first URL.)
4854 * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
4857 var i
, j
, urlResult
= 0, result
;
4859 if (url
instanceof Array
) {
4861 // find the first good one
4862 for (i
=0, j
=url
.length
; i
<j
; i
++) {
4864 if (url
[i
] instanceof Object
) {
4866 if (sm2
.canPlayMIME(url
[i
].type
)) {
4871 } else if (sm2
.canPlayURL(url
[i
])) {
4879 // normalize to string
4880 if (url
[urlResult
].url
) {
4881 url
[urlResult
] = url
[urlResult
].url
;
4884 result
= url
[urlResult
];
4898 startTimer = function(oSound
) {
4901 * attach a timer to this sound, and start an interval if needed
4904 if (!oSound
._hasTimer
) {
4906 oSound
._hasTimer
= true;
4908 if (!mobileHTML5
&& sm2
.html5PollingInterval
) {
4910 if (h5IntervalTimer
=== null && h5TimerCount
=== 0) {
4912 h5IntervalTimer
= setInterval(timerExecute
, sm2
.html5PollingInterval
);
4924 stopTimer = function(oSound
) {
4930 if (oSound
._hasTimer
) {
4932 oSound
._hasTimer
= false;
4934 if (!mobileHTML5
&& sm2
.html5PollingInterval
) {
4936 // interval will stop itself at next execution.
4946 timerExecute = function() {
4949 * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)
4954 if (h5IntervalTimer
!== null && !h5TimerCount
) {
4956 // no active timers, stop polling interval.
4958 clearInterval(h5IntervalTimer
);
4960 h5IntervalTimer
= null;
4966 // check all HTML5 sounds with timers
4968 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
4970 if (sm2
.sounds
[sm2
.soundIDs
[i
]].isHTML5
&& sm2
.sounds
[sm2
.soundIDs
[i
]]._hasTimer
) {
4972 sm2
.sounds
[sm2
.soundIDs
[i
]]._onTimer();
4980 catchError = function(options
) {
4982 options
= (options
!== _undefined
? options : {});
4984 if (typeof sm2
.onerror
=== 'function') {
4985 sm2
.onerror
.apply(window
, [{type:(options
.type
!== _undefined
? options
.type : null)}]);
4988 if (options
.fatal
!== _undefined
&& options
.fatal
) {
4994 badSafariFix = function() {
4996 // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4
4997 if (!isBadSafari
|| !detectFlash()) {
5002 var aF
= sm2
.audioFormats
, i
, item
;
5005 if (aF
.hasOwnProperty(item
)) {
5006 if (item
=== 'mp3' || item
=== 'mp4') {
5007 sm2
._wD(sm
+ ': Using flash fallback for ' + item
+ ' format');
5008 sm2
.html5
[item
] = false;
5009 // assign result to related formats, too
5010 if (aF
[item
] && aF
[item
].related
) {
5011 for (i
= aF
[item
].related
.length
-1; i
>= 0; i
--) {
5012 sm2
.html5
[aF
[item
].related
[i
]] = false;
5022 * Pseudo-private flash/ExternalInterface methods
5023 * ----------------------------------------------
5026 this._setSandboxType = function(sandboxType
) {
5029 var sb
= sm2
.sandbox
;
5031 sb
.type
= sandboxType
;
5032 sb
.description
= sb
.types
[(sb
.types
[sandboxType
] !== _undefined
?sandboxType:'unknown')];
5034 if (sb
.type
=== 'localWithFile') {
5040 } else if (sb
.type
=== 'localWithNetwork') {
5042 sb
.noRemote
= false;
5045 } else if (sb
.type
=== 'localTrusted') {
5047 sb
.noRemote
= false;
5055 this._externalInterfaceOK = function(swfVersion
) {
5057 // flash callback confirming flash loaded, EI working etc.
5058 // swfVersion: SWF build string
5060 if (sm2
.swfLoaded
) {
5066 debugTS('swf', true);
5067 debugTS('flashtojs', true);
5068 sm2
.swfLoaded
= true;
5069 tryInitOnFocus
= false;
5075 // complain if JS + SWF build/version strings don't match, excluding +DEV builds
5077 if (!swfVersion
|| swfVersion
.replace(/\+dev/i,'') !== sm2
.versionNumber
.replace(/\+dev/i, '')) {
5079 e
= sm
+ ': Fatal: JavaScript file build "' + sm2
.versionNumber
+ '" does not match Flash SWF build "' + swfVersion
+ '" at ' + sm2
.url
+ '. Ensure both are up-to-date.';
5081 // escape flash -> JS stack so this error fires in window.
5082 setTimeout(function versionMismatch() {
5086 // exit, init will fail with timeout
5092 // IE needs a larger timeout
5093 setTimeout(init
, isIE
? 100 : 1);
5098 * Private initialization helpers
5099 * ------------------------------
5102 createMovie = function(smID
, smURL
) {
5104 if (didAppend
&& appendSuccess
) {
5105 // ignore if already succeeded
5109 function initMsg() {
5118 title
= 'SoundManager ' + sm2
.version
+ (!sm2
.html5Only
&& sm2
.useHTML5Audio
? (sm2
.hasHTML5
? ' + HTML5 audio' : ', no HTML5 audio support') : '');
5120 if (!sm2
.html5Only
) {
5122 if (sm2
.preferFlash
) {
5123 options
.push('preferFlash');
5126 if (sm2
.useHighPerformance
) {
5127 options
.push('useHighPerformance');
5130 if (sm2
.flashPollingInterval
) {
5131 options
.push('flashPollingInterval (' + sm2
.flashPollingInterval
+ 'ms)');
5134 if (sm2
.html5PollingInterval
) {
5135 options
.push('html5PollingInterval (' + sm2
.html5PollingInterval
+ 'ms)');
5139 options
.push('wmode (' + sm2
.wmode
+ ')');
5142 if (sm2
.debugFlash
) {
5143 options
.push('debugFlash');
5146 if (sm2
.useFlashBlock
) {
5147 options
.push('flashBlock');
5152 if (sm2
.html5PollingInterval
) {
5153 options
.push('html5PollingInterval (' + sm2
.html5PollingInterval
+ 'ms)');
5158 if (options
.length
) {
5159 msg
= msg
.concat([options
.join(delimiter
)]);
5162 sm2
._wD(title
+ (msg
.length
? delimiter
+ msg
.join(', ') : ''), 1);
5170 if (sm2
.html5Only
) {
5176 sm2
.oMC
= id(sm2
.movieID
);
5179 // prevent multiple init attempts
5182 appendSuccess
= true;
5189 var remoteURL
= (smURL
|| sm2
.url
),
5190 localURL
= (sm2
.altURL
|| remoteURL
),
5191 swfTitle
= 'JS/Flash audio component (SoundManager 2)',
5192 oTarget
= getDocument(),
5193 extraClass
= getSWFCSS(),
5195 html
= doc
.getElementsByTagName('html')[0],
5196 oEmbed
, oMovie
, tmp
, movieHTML
, oEl
, s
, x
, sClass
;
5198 isRTL
= (html
&& html
.dir
&& html
.dir
.match(/rtl/i));
5199 smID
= (smID
=== _undefined
?sm2
.id:smID
);
5201 function param(name
, value
) {
5202 return '<param name="'+name
+'" value="'+value
+'" />';
5205 // safety check for legacy (change to Flash 9 URL)
5207 sm2
.url
= normalizeMovieURL(overHTTP
?remoteURL:localURL
);
5210 sm2
.wmode
= (!sm2
.wmode
&& sm2
.useHighPerformance
? 'transparent' : sm2
.wmode
);
5212 if (sm2
.wmode
!== null && (ua
.match(/msie
8/i
) || (!isIE
&& !sm2
.useHighPerformance
)) && navigator
.platform
.match(/win32|win64/i)) {
5214 * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here
5215 * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout
5216 * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)
5218 messages
.push(strings
.spcWmode
);
5227 'allowScriptAccess': sm2
.allowScriptAccess
,
5228 'bgcolor': sm2
.bgColor
,
5229 'pluginspage': http
+'www.macromedia.com/go/getflashplayer',
5231 'type': 'application/x-shockwave-flash',
5233 // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
5234 'hasPriority': 'true'
5237 if (sm2
.debugFlash
) {
5238 oEmbed
.FlashVars
= 'debug=1';
5242 // don't write empty attribute
5243 delete oEmbed
.wmode
;
5249 oMovie
= doc
.createElement('div');
5251 '<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">',
5252 param('movie', smURL
),
5253 param('AllowScriptAccess', sm2
.allowScriptAccess
),
5254 param('quality', oEmbed
.quality
),
5255 (sm2
.wmode
? param('wmode', sm2
.wmode
): ''),
5256 param('bgcolor', sm2
.bgColor
),
5257 param('hasPriority', 'true'),
5258 (sm2
.debugFlash
? param('FlashVars', oEmbed
.FlashVars
) : ''),
5264 oMovie
= doc
.createElement('embed');
5265 for (tmp
in oEmbed
) {
5266 if (oEmbed
.hasOwnProperty(tmp
)) {
5267 oMovie
.setAttribute(tmp
, oEmbed
[tmp
]);
5274 extraClass
= getSWFCSS();
5275 oTarget
= getDocument();
5279 sm2
.oMC
= (id(sm2
.movieID
) || doc
.createElement('div'));
5283 sm2
.oMC
.id
= sm2
.movieID
;
5284 sm2
.oMC
.className
= swfCSS
.swfDefault
+ ' ' + extraClass
;
5288 if (!sm2
.useFlashBlock
) {
5289 if (sm2
.useHighPerformance
) {
5290 // on-screen at all times
5292 'position': 'fixed',
5295 // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.
5298 'overflow': 'hidden'
5301 // hide off-screen, lower priority
5303 'position': 'absolute',
5310 s
.left
= Math
.abs(parseInt(s
.left
,10))+'px';
5316 // soundcloud-reported render/crash fix, safari 5
5317 sm2
.oMC
.style
.zIndex
= 10000;
5320 if (!sm2
.debugFlash
) {
5322 if (s
.hasOwnProperty(x
)) {
5323 sm2
.oMC
.style
[x
] = s
[x
];
5330 sm2
.oMC
.appendChild(oMovie
);
5332 oTarget
.appendChild(sm2
.oMC
);
5334 oEl
= sm2
.oMC
.appendChild(doc
.createElement('div'));
5335 oEl
.className
= swfCSS
.swfBox
;
5336 oEl
.innerHTML
= movieHTML
;
5338 appendSuccess
= true;
5340 throw new Error(str('domError')+' \n'+e
.toString());
5345 // SM2 container is already in the document (eg. flashblock use case)
5346 sClass
= sm2
.oMC
.className
;
5347 sm2
.oMC
.className
= (sClass
?sClass
+' ':swfCSS
.swfDefault
) + (extraClass
?' '+extraClass:'');
5348 sm2
.oMC
.appendChild(oMovie
);
5350 oEl
= sm2
.oMC
.appendChild(doc
.createElement('div'));
5351 oEl
.className
= swfCSS
.swfBox
;
5352 oEl
.innerHTML
= movieHTML
;
5354 appendSuccess
= true;
5362 // sm2._wD(sm + ': Trying to load ' + smURL + (!overHTTP && sm2.altURL ? ' (alternate URL)' : ''), 1);
5368 initMovie = function() {
5370 if (sm2
.html5Only
) {
5375 // attempt to get, or create, movie (may already exist)
5383 * Something isn't right - we've reached init, but the soundManager url property has not been set.
5384 * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.
5385 * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.
5393 // inline markup case
5394 flash
= sm2
.getMovie(sm2
.id
);
5399 createMovie(sm2
.id
, sm2
.url
);
5401 // try to re-append removed movie after reboot()
5403 sm2
.oMC
.appendChild(oRemoved
);
5405 sm2
.oMC
.innerHTML
= oRemovedHTML
;
5410 flash
= sm2
.getMovie(sm2
.id
);
5413 if (typeof sm2
.oninitmovie
=== 'function') {
5414 setTimeout(sm2
.oninitmovie
, 1);
5425 delayWaitForEI = function() {
5427 setTimeout(waitForEI
, 1000);
5431 rebootIntoHTML5 = function() {
5433 // special case: try for a reboot with preferFlash: false, if 100% HTML5 mode is possible and useFlashBlock is not enabled.
5435 window
.setTimeout(function() {
5437 complain(smc
+ 'useFlashBlock is false, 100% HTML5 mode is possible. Rebooting with preferFlash: false...');
5443 // if for some reason you want to detect this case, use an ontimeout() callback and look for html5Only and didFlashBlock == true.
5444 sm2
.didFlashBlock
= true;
5446 sm2
.beginDelayedInit();
5452 waitForEI = function() {
5455 loadIncomplete
= false;
5458 // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.
5466 waitingForEI
= true;
5467 event
.remove(window
, 'load', delayWaitForEI
);
5469 if (hasFlash
&& tryInitOnFocus
&& !isFocused
) {
5470 // Safari won't load flash in background tabs, only when focused.
5476 p
= sm2
.getMoviePercent();
5477 if (p
> 0 && p
< 100) {
5478 loadIncomplete
= true;
5482 setTimeout(function() {
5484 p
= sm2
.getMoviePercent();
5486 if (loadIncomplete
) {
5487 // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
5488 waitingForEI
= false;
5489 sm2
._wD(str('waitSWF'));
5490 window
.setTimeout(delayWaitForEI
, 1);
5497 sm2
._wD(sm
+ ': No Flash response within expected time. Likely causes: ' + (p
=== 0 ? 'SWF load failed, ':'') + 'Flash blocked or JS-Flash security error.' + (sm2
.debugFlash
?' ' + str('checkSWF'):''), 2);
5499 if (!overHTTP
&& p
) {
5501 _wDS('localFail', 2);
5503 if (!sm2
.debugFlash
) {
5504 _wDS('tryDebug', 2);
5511 // if 0 (not null), probably a 404.
5512 sm2
._wD(str('swf404', sm2
.url
), 1);
5516 debugTS('flashtojs', false, ': Timed out' + overHTTP
?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');
5521 // give up / time-out, depending
5523 if (!didInit
&& okToDisable
) {
5527 // SWF failed to report load progress. Possibly blocked.
5529 if (sm2
.useFlashBlock
|| sm2
.flashLoadTimeout
=== 0) {
5531 if (sm2
.useFlashBlock
) {
5533 flashBlockHandler();
5537 _wDS('waitForever');
5541 // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.
5543 if (!sm2
.useFlashBlock
&& canIgnoreFlash
) {
5549 _wDS('waitForever');
5551 // fire any regular registered ontimeout() listeners.
5552 processOnEvents({type:'ontimeout', ignoreInit: true, error: {type: 'INIT_FLASHBLOCK'}});
5560 // SWF loaded? Shouldn't be a blocking issue, then.
5562 if (sm2
.flashLoadTimeout
=== 0) {
5564 _wDS('waitForever');
5568 if (!sm2
.useFlashBlock
&& canIgnoreFlash
) {
5584 }, sm2
.flashLoadTimeout
);
5588 handleFocus = function() {
5590 function cleanup() {
5591 event
.remove(window
, 'focus', handleFocus
);
5594 if (isFocused
|| !tryInitOnFocus
) {
5595 // already focused, or not special Safari background tab case
5604 // allow init to restart
5605 waitingForEI
= false;
5607 // kick off ExternalInterface timeout, now that the SWF has started
5615 flushMessages = function() {
5619 // SM2 pre-init debug messages
5620 if (messages
.length
) {
5621 sm2
._wD('SoundManager 2: ' + messages
.join(' '), 1);
5629 showSupport = function() {
5635 var item
, tests
= [];
5637 if (sm2
.useHTML5Audio
&& sm2
.hasHTML5
) {
5638 for (item
in sm2
.audioFormats
) {
5639 if (sm2
.audioFormats
.hasOwnProperty(item
)) {
5640 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)' : ''))));
5643 sm2
._wD('SoundManager 2 HTML5 support: ' + tests
.join(', '), 1);
5650 initComplete = function(bNoDisable
) {
5656 if (sm2
.html5Only
) {
5661 debugTS('onload', true);
5665 var wasTimeout
= (sm2
.useFlashBlock
&& sm2
.flashLoadTimeout
&& !sm2
.getMoviePercent()),
5673 error
= {type: (!hasFlash
&& needsFlash
? 'NO_FLASH' : 'INIT_TIMEOUT')};
5675 sm2
._wD('SoundManager 2 ' + (disabled
? 'failed to load' : 'loaded') + ' (' + (disabled
? 'Flash security/load error' : 'OK') + ')', disabled
? 2: 1);
5677 if (disabled
|| bNoDisable
) {
5678 if (sm2
.useFlashBlock
&& sm2
.oMC
) {
5679 sm2
.oMC
.className
= getSWFCSS() + ' ' + (sm2
.getMoviePercent() === null?swfCSS
.swfTimedout:swfCSS
.swfError
);
5681 processOnEvents({type:'ontimeout', error:error
, ignoreInit: true});
5682 debugTS('onload', false);
5686 debugTS('onload', true);
5690 if (sm2
.waitForWindowLoad
&& !windowLoaded
) {
5692 event
.add(window
, 'load', initUserOnload
);
5695 if (sm2
.waitForWindowLoad
&& windowLoaded
) {
5708 * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
5709 * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
5712 setProperties = function() {
5715 o
= sm2
.setupOptions
;
5719 if (o
.hasOwnProperty(i
)) {
5721 // assign local property if not already defined
5723 if (sm2
[i
] === _undefined
) {
5727 } else if (sm2
[i
] !== o
[i
]) {
5729 // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
5730 sm2
.setupOptions
[i
] = sm2
[i
];
5743 // called after onload()
5750 function cleanup() {
5751 event
.remove(window
, 'load', sm2
.beginDelayedInit
);
5754 if (sm2
.html5Only
) {
5756 // we don't need no steenking flash!
5769 // attempt to talk to Flash
5770 flash
._externalInterfaceTest(false);
5772 // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling
5773 // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)
5774 setPolling(true, (sm2
.flashPollingInterval
|| (sm2
.useHighPerformance
? 10 : 50)));
5776 if (!sm2
.debugMode
) {
5777 // stop the SWF from making debug output calls to JS
5778 flash
._disableDebug();
5782 debugTS('jstoflash', true);
5784 if (!sm2
.html5Only
) {
5785 // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead
5786 // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
5787 event
.add(window
, 'unload', doNothing
);
5792 sm2
._wD('js/flash exception: ' + e
.toString());
5793 debugTS('jstoflash', false);
5794 catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true});
5795 // don't disable, for reboot()
5805 // disconnect events
5812 domContentLoaded = function() {
5820 // assign top-level soundManager properties eg. soundManager.url
5826 * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1
5827 * Ditto for sm2-preferFlash, too.
5832 var a
= 'sm2-usehtml5audio=',
5833 a2
= 'sm2-preferflash=',
5836 l
= wl
.toLowerCase();
5838 if (l
.indexOf(a
) !== -1) {
5839 b
= (l
.charAt(l
.indexOf(a
)+a
.length
) === '1');
5841 console
.log((b
?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
5848 if (l
.indexOf(a2
) !== -1) {
5849 b2
= (l
.charAt(l
.indexOf(a2
)+a2
.length
) === '1');
5851 console
.log((b2
?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
5861 if (!hasFlash
&& sm2
.hasHTML5
) {
5862 sm2
._wD('SoundManager 2: No Flash detected' + (!sm2
.useHTML5Audio
? ', enabling HTML5.' : '. Trying HTML5-only mode.'), 1);
5864 'useHTML5Audio': true,
5865 // make sure we aren't preferring flash, either
5866 // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
5867 'preferFlash': false
5873 if (!hasFlash
&& needsFlash
) {
5874 messages
.push(strings
.needFlash
);
5875 // TODO: Fatal here vs. timeout approach, etc.
5876 // hack: fail sooner.
5878 'flashLoadTimeout': 1
5882 if (doc
.removeEventListener
) {
5883 doc
.removeEventListener('DOMContentLoaded', domContentLoaded
, false);
5892 domContentLoadedIE = function() {
5894 if (doc
.readyState
=== 'complete') {
5896 doc
.detachEvent('onreadystatechange', domContentLoadedIE
);
5903 winOnLoad = function() {
5905 // catch edge case of initComplete() firing after window.load()
5906 windowLoaded
= true;
5907 event
.remove(window
, 'load', winOnLoad
);
5912 * miscellaneous run-time, pre-init stuff
5915 preInit = function() {
5919 // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
5922 if (!sm2
.setupOptions
.useHTML5Audio
|| sm2
.setupOptions
.preferFlash
) {
5923 // notify that defaults are being changed.
5924 messages
.push(strings
.mobileUA
);
5928 sm2
.setupOptions
.useHTML5Audio
= true;
5929 sm2
.setupOptions
.preferFlash
= false;
5931 if (is_iDevice
|| (isAndroid
&& !ua
.match(/android\s2\.3/i))) {
5932 // iOS and Android devices tend to work better with a single audio instance, specifically for chained playback of sounds in sequence.
5933 // common use case: exiting sound onfinish() -> createSound() -> play()
5935 messages
.push(strings
.globalHTML5
);
5938 sm2
.ignoreFlash
= true;
5940 useGlobalHTML5Audio
= true;
5952 // focus and window load, init (primarily flash-driven)
5953 event
.add(window
, 'focus', handleFocus
);
5954 event
.add(window
, 'load', delayWaitForEI
);
5955 event
.add(window
, 'load', winOnLoad
);
5957 if (doc
.addEventListener
) {
5959 doc
.addEventListener('DOMContentLoaded', domContentLoaded
, false);
5961 } else if (doc
.attachEvent
) {
5963 doc
.attachEvent('onreadystatechange', domContentLoadedIE
);
5967 // no add/attachevent support - safe to assume no JS -> Flash either
5968 debugTS('onload', false);
5969 catchError({type:'NO_DOM2_EVENTS', fatal:true});
5975 // SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading
5977 if (window
.SM2_DEFER
=== undefined || !SM2_DEFER
) {
5978 soundManager
= new SoundManager();
5982 * SoundManager public interfaces
5983 * ------------------------------
5986 window
.SoundManager
= SoundManager
; // constructor
5987 window
.soundManager
= soundManager
; // public API, flash callbacks etc.