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
11 * V2.97a.20130324 ("Mahalo" Edition)
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': true, // overrides useHTML5audio. if true and flash support present, will try to use flash for MP3/MP4 as needed since HTML5 audio support is still quirky in browsers.
77 'noSWFCache': false // if true, appends ?ts={date} to break aggressive SWF caching.
81 this.defaultOptions
= {
84 * the default configuration for sound objects made with createSound() and related methods
85 * eg., volume, auto-load behaviour and so forth
88 '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)
89 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true)
90 'from': null, // position to start playback within a sound (msec), default = beginning
91 'loops': 1, // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)
92 'onid3': null, // callback function for "ID3 data is added/available"
93 'onload': null, // callback function for "load finished"
94 'whileloading': null, // callback function for "download progress update" (X of Y bytes received)
95 'onplay': null, // callback for "play" start
96 'onpause': null, // callback for "pause"
97 'onresume': null, // callback for "resume" (pause toggle)
98 'whileplaying': null, // callback during play (position update)
99 'onposition': null, // object containing times and function callbacks for positions of interest
100 'onstop': null, // callback for "user stop"
101 'onfailure': null, // callback function for when playing fails
102 'onfinish': null, // callback function for "sound finished playing"
103 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
104 'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled
105 'position': null, // offset (milliseconds) to seek to within loaded sound data.
106 'pan': 0, // "pan" settings, left-to-right, -100 to 100
107 'stream': true, // allows playing before entire file has loaded (recommended)
108 'to': null, // position to end playback within a sound (msec), default = end
109 'type': null, // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3
110 'usePolicyFile': false, // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)
111 'volume': 100 // self-explanatory. 0-100, the latter being the max.
115 this.flash9Options
= {
118 * flash 9-only options,
119 * merged into defaultOptions if flash 9 is being used
122 'isMovieStar': null, // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL
123 'usePeakData': false, // enable left/right channel peak (level) data
124 'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.
125 'useEQData': false, // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.
126 'onbufferchange': null, // callback for "isBuffering" property change
127 'ondataerror': null // callback for waveform/eq data access error (flash playing audio in other tabs/domains)
131 this.movieStarOptions
= {
134 * flash 9.0r115+ MPEG4 audio options,
135 * merged into defaultOptions if flash 9+movieStar mode is enabled
138 'bufferTime': 3, // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)
139 'serverURL': null, // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants
140 'onconnect': null, // rtmp: callback for connection to flash media server
141 'duration': null // rtmp: song duration (msec)
145 this.audioFormats
= {
148 * determines HTML5 support + flash requirements.
149 * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
150 * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
154 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
159 'related': ['aac','m4a','m4b'], // additional formats under the MP4 container
160 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
165 'type': ['audio/ogg; codecs=vorbis'],
170 'type': ['audio/ogg; codecs=opus', 'audio/opus'],
175 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
181 // HTML attributes (id + class names) for the SWF container
183 this.movieID
= 'sm2-container';
184 this.id
= (smID
|| 'sm2movie');
186 this.debugID
= 'soundmanager-debug';
187 this.debugURLParam
= /([#?&])debug=1/i;
189 // dynamic attributes
191 this.versionNumber
= 'V2.97a.20130324';
193 this.movieURL
= null;
195 this.swfLoaded
= false;
196 this.enabled
= false;
201 this.didFlashBlock
= false;
202 this.filePattern
= null;
204 this.filePatterns
= {
206 'flash8': /\.mp3(\?.*)?$/i,
207 'flash9': /\.mp3(\?.*)?$/i
211 // support indicators, set at init
217 'waveformData': false,
223 // flash sandbox info, used primarily in troubleshooting
230 'remote': 'remote (domain-based) rules',
231 'localWithFile': 'local with file access (no internet access)',
232 'localWithNetwork': 'local with network (internet access only, no local access)',
233 'localTrusted': 'local, trusted (local+internet access)'
243 * format support (html5/flash)
244 * stores canPlayType() results based on audioFormats.
245 * eg. { mp3: boolean, mp4: boolean }
246 * treat as read-only.
250 'usingFlash': null // set if/when flash fallback is needed
253 // file type support hash
256 // determined at init time
257 this.html5Only
= false;
259 // used for special cases (eg. iPad/iPhone/palm OS?)
260 this.ignoreFlash
= false;
263 * a few private internals (OK, a lot. :D)
267 sm2
= this, globalHTML5Audio
= null, flash
= null, sm
= 'soundManager', smc
= sm
+ ': ', h5
= 'HTML5::', id
, ua
= navigator
.userAgent
, wl
= window
.location
.href
.toString(), doc
= document
, doNothing
, setProperties
, init
, fV
, on_queue
= [], debugOpen
= true, debugTS
, didAppend
= false, appendSuccess
= false, didInit
= false, disabled
= false, windowLoaded
= false, _wDS
, wdCount
= 0, initComplete
, mixin
, assign
, extraOptions
, addOnEvent
, processOnEvents
, initUserOnload
, delayWaitForEI
, waitForEI
, setVersionInfo
, handleFocus
, strings
, initMovie
, preInit
, domContentLoaded
, winOnLoad
, didDCLoaded
, getDocument
, createMovie
, catchError
, setPolling
, initDebug
, debugLevels
= ['log', 'info', 'warn', 'error'], defaultFlashVersion
= 8, disableObject
, failSafely
, normalizeMovieURL
, oRemoved
= null, oRemovedHTML
= null, str
, flashBlockHandler
, getSWFCSS
, swfCSS
, toggleDebug
, loopFix
, policyFix
, complain
, idCheck
, waitingForEI
= false, initPending
= false, startTimer
, stopTimer
, timerExecute
, h5TimerCount
= 0, h5IntervalTimer
= null, parseURL
, messages
= [],
268 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
,
269 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)),
270 mobileHTML5
= (ua
.match(/(mobile|pre\/|xoom)/i) || is_iDevice
|| isAndroid
),
271 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
272 hasConsole
= (window
.console
!== _undefined
&& console
.log
!== _undefined
), isFocused
= (doc
.hasFocus
!== _undefined
?doc
.hasFocus():null), tryInitOnFocus
= (isSafari
&& (doc
.hasFocus
=== _undefined
|| !doc
.hasFocus())), okToDisable
= !tryInitOnFocus
, flashMIME
= /(mp3|mp4|mpa|m4a|m4b)/i,
273 emptyURL
= 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
274 overHTTP
= (doc
.location
?doc
.location
.protocol
.match(/http/i):null),
275 http
= (!overHTTP
? 'http:/'+'/' : ''),
276 // mp3, mp4, aac etc.
277 netStreamMimeTypes
= /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,
278 // Flash v9.0r115+ "moviestar" formats
279 netStreamTypes
= ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],
280 netStreamPattern
= new RegExp('\\.(' + netStreamTypes
.join('|') + ')(\\?.*)?$', 'i');
282 this.mimePattern
= /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set
284 // use altURL if not "online"
285 this.useAltURL
= !overHTTP
;
289 'swfBox': 'sm2-object-box',
290 'swfDefault': 'movieContainer',
291 'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)
292 'swfTimedout': 'swf_timedout',
293 'swfLoaded': 'swf_loaded',
294 'swfUnblocked': 'swf_unblocked', // or loaded OK
295 'sm2Debug': 'sm2_debug',
296 'highPerf': 'high_performance',
297 'flashDebug': 'flash_debug'
302 * basic HTML5 Audio() support test
303 * try...catch because of IE 9 "not implemented" nonsense
304 * https://github.com/Modernizr/Modernizr/issues/224
307 this.hasHTML5
= (function() {
309 // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.
310 return (Audio
!== _undefined
&& (isOpera
&& opera
!== _undefined
&& opera
.version() < 10 ? new Audio(null) : new Audio()).canPlayType
!== _undefined
);
317 * Public SoundManager API
318 * -----------------------
322 * Configures top-level soundManager properties.
324 * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
325 * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
328 this.setup = function(options
) {
330 var noURL
= (!sm2
.url
);
332 // warn if flash options have already been applied
334 if (options
!== _undefined
&& didInit
&& needsFlash
&& sm2
.ok() && (options
.flashVersion
!== _undefined
|| options
.url
!== _undefined
|| options
.html5Test
!== _undefined
)) {
335 complain(str('setupLate'));
338 // TODO: defer: true?
342 // 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.
344 if (noURL
&& didDCLoaded
&& options
.url
!== _undefined
) {
345 sm2
.beginDelayedInit();
348 // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
350 if (!didDCLoaded
&& options
.url
!== _undefined
&& doc
.readyState
=== 'complete') {
351 setTimeout(domContentLoaded
, 1);
358 this.ok = function() {
360 return (needsFlash
?(didInit
&& !disabled
):(sm2
.useHTML5Audio
&& sm2
.hasHTML5
));
364 this.supported
= this.ok
; // legacy
366 this.getMovie = function(smID
) {
368 // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version
369 return id(smID
) || doc
[smID
] || window
[smID
];
374 * Creates a SMSound sound object instance.
376 * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
377 * @return {object} SMSound The new SMSound object.
380 this.createSound = function(oOptions
, _url
) {
382 var cs
, cs_string
, options
, oSound
= null;
385 cs
= sm
+ '.createSound(): ';
386 cs_string
= cs
+ str(!didInit
?'notReady':'notOK');
389 if (!didInit
|| !sm2
.ok()) {
394 if (_url
!== _undefined
) {
395 // function overloading in JS! :) ..assume simple createSound(id,url) use case
402 // inherit from defaultOptions
403 options
= mixin(oOptions
);
405 options
.url
= parseURL(options
.url
);
408 if (options
.id
.toString().charAt(0).match(/^[0-9]$/)) {
409 sm2
._wD(cs
+ str('badID', options
.id
), 2);
412 sm2
._wD(cs
+ options
.id
+ ' (' + options
.url
+ ')', 1);
415 if (idCheck(options
.id
, true)) {
416 sm2
._wD(cs
+ options
.id
+ ' exists', 1);
417 return sm2
.sounds
[options
.id
];
422 options
= loopFix(options
);
423 sm2
.sounds
[options
.id
] = new SMSound(options
);
424 sm2
.soundIDs
.push(options
.id
);
425 return sm2
.sounds
[options
.id
];
429 if (html5OK(options
)) {
432 sm2
._wD(options
.id
+ ': Using HTML5');
433 oSound
._setup_html5(options
);
438 if (options
.isMovieStar
=== null) {
439 // attempt to detect MPEG-4 formats
440 options
.isMovieStar
= !!(options
.serverURL
|| (options
.type
? options
.type
.match(netStreamMimeTypes
) : false) || options
.url
.match(netStreamPattern
));
443 if (options
.isMovieStar
) {
444 sm2
._wD(cs
+ 'using MovieStar handling');
445 if (options
.loops
> 1) {
452 options
= policyFix(options
, cs
);
456 flash
._createSound(options
.id
, options
.loops
||1, options
.usePolicyFile
);
458 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
);
459 if (!options
.serverURL
) {
460 // We are connected immediately
461 oSound
.connected
= true;
462 if (options
.onconnect
) {
463 options
.onconnect
.apply(oSound
);
468 if (!options
.serverURL
&& (options
.autoLoad
|| options
.autoPlay
)) {
469 // call load for non-rtmp streams
470 oSound
.load(options
);
475 // rtmp will play in onconnect
476 if (!options
.serverURL
&& options
.autoPlay
) {
485 * Destroys a SMSound sound object instance.
487 * @param {string} sID The ID of the sound to destroy
490 this.destroySound = function(sID
, _bFromSound
) {
492 // explicitly destroy a sound before normal page unload, etc.
498 var oS
= sm2
.sounds
[sID
], i
;
500 // Disable all callbacks while the sound is being destroyed
506 for (i
= 0; i
< sm2
.soundIDs
.length
; i
++) {
507 if (sm2
.soundIDs
[i
] === sID
) {
508 sm2
.soundIDs
.splice(i
, 1);
514 // ignore if being called from SMSound instance
519 delete sm2
.sounds
[sID
];
526 * Calls the load() method of a SMSound object by ID.
528 * @param {string} sID The ID of the sound
529 * @param {object} oOptions Optional: Sound options
532 this.load = function(sID
, oOptions
) {
537 return sm2
.sounds
[sID
].load(oOptions
);
542 * Calls the unload() method of a SMSound object by ID.
544 * @param {string} sID The ID of the sound
547 this.unload = function(sID
) {
552 return sm2
.sounds
[sID
].unload();
557 * Calls the onPosition() method of a SMSound object by ID.
559 * @param {string} sID The ID of the sound
560 * @param {number} nPosition The position to watch for
561 * @param {function} oMethod The relevant callback to fire
562 * @param {object} oScope Optional: The scope to apply the callback to
563 * @return {SMSound} The SMSound object
566 this.onPosition = function(sID
, nPosition
, oMethod
, oScope
) {
571 return sm2
.sounds
[sID
].onposition(nPosition
, oMethod
, oScope
);
575 // legacy/backwards-compability: lower-case method name
576 this.onposition
= this.onPosition
;
579 * Calls the clearOnPosition() method of a SMSound object by ID.
581 * @param {string} sID The ID of the sound
582 * @param {number} nPosition The position to watch for
583 * @param {function} oMethod Optional: The relevant callback to fire
584 * @return {SMSound} The SMSound object
587 this.clearOnPosition = function(sID
, nPosition
, oMethod
) {
592 return sm2
.sounds
[sID
].clearOnPosition(nPosition
, oMethod
);
597 * Calls the play() method of a SMSound object by ID.
599 * @param {string} sID The ID of the sound
600 * @param {object} oOptions Optional: Sound options
601 * @return {SMSound} The SMSound object
604 this.play = function(sID
, oOptions
) {
608 if (!didInit
|| !sm2
.ok()) {
609 complain(sm
+ '.play(): ' + str(!didInit
?'notReady':'notOK'));
614 if (!(oOptions
instanceof Object
)) {
615 // overloading use case: play('mySound','/path/to/some.mp3');
620 if (oOptions
&& oOptions
.url
) {
621 // overloading use case, create+play: .play('someID',{url:'/path/to.mp3'});
622 sm2
._wD(sm
+ '.play(): attempting to create "' + sID
+ '"', 1);
624 result
= sm2
.createSound(oOptions
).play();
629 return sm2
.sounds
[sID
].play(oOptions
);
633 this.start
= this.play
; // just for convenience
636 * Calls the setPosition() method of a SMSound object by ID.
638 * @param {string} sID The ID of the sound
639 * @param {number} nMsecOffset Position (milliseconds)
640 * @return {SMSound} The SMSound object
643 this.setPosition = function(sID
, nMsecOffset
) {
648 return sm2
.sounds
[sID
].setPosition(nMsecOffset
);
653 * Calls the stop() method of a SMSound object by ID.
655 * @param {string} sID The ID of the sound
656 * @return {SMSound} The SMSound object
659 this.stop = function(sID
) {
665 sm2
._wD(sm
+ '.stop(' + sID
+ ')', 1);
666 return sm2
.sounds
[sID
].stop();
671 * Stops all currently-playing sounds.
674 this.stopAll = function() {
677 sm2
._wD(sm
+ '.stopAll()', 1);
679 for (oSound
in sm2
.sounds
) {
680 if (sm2
.sounds
.hasOwnProperty(oSound
)) {
681 // apply only to sound objects
682 sm2
.sounds
[oSound
].stop();
689 * Calls the pause() method of a SMSound object by ID.
691 * @param {string} sID The ID of the sound
692 * @return {SMSound} The SMSound object
695 this.pause = function(sID
) {
700 return sm2
.sounds
[sID
].pause();
705 * Pauses all currently-playing sounds.
708 this.pauseAll = function() {
711 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
712 sm2
.sounds
[sm2
.soundIDs
[i
]].pause();
718 * Calls the resume() method of a SMSound object by ID.
720 * @param {string} sID The ID of the sound
721 * @return {SMSound} The SMSound object
724 this.resume = function(sID
) {
729 return sm2
.sounds
[sID
].resume();
734 * Resumes all currently-paused sounds.
737 this.resumeAll = function() {
740 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
741 sm2
.sounds
[sm2
.soundIDs
[i
]].resume();
747 * Calls the togglePause() method of a SMSound object by ID.
749 * @param {string} sID The ID of the sound
750 * @return {SMSound} The SMSound object
753 this.togglePause = function(sID
) {
758 return sm2
.sounds
[sID
].togglePause();
763 * Calls the setPan() method of a SMSound object by ID.
765 * @param {string} sID The ID of the sound
766 * @param {number} nPan The pan value (-100 to 100)
767 * @return {SMSound} The SMSound object
770 this.setPan = function(sID
, nPan
) {
775 return sm2
.sounds
[sID
].setPan(nPan
);
780 * Calls the setVolume() method of a SMSound object by ID.
782 * @param {string} sID The ID of the sound
783 * @param {number} nVol The volume value (0 to 100)
784 * @return {SMSound} The SMSound object
787 this.setVolume = function(sID
, nVol
) {
792 return sm2
.sounds
[sID
].setVolume(nVol
);
797 * Calls the mute() method of either a single SMSound object by ID, or all sound objects.
799 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
802 this.mute = function(sID
) {
806 if (sID
instanceof String
) {
812 sm2
._wD(sm
+ '.mute(): Muting all sounds');
813 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
814 sm2
.sounds
[sm2
.soundIDs
[i
]].mute();
823 sm2
._wD(sm
+ '.mute(): Muting "' + sID
+ '"');
824 return sm2
.sounds
[sID
].mute();
836 this.muteAll = function() {
843 * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.
845 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
848 this.unmute = function(sID
) {
852 if (sID
instanceof String
) {
858 sm2
._wD(sm
+ '.unmute(): Unmuting all sounds');
859 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
860 sm2
.sounds
[sm2
.soundIDs
[i
]].unmute();
869 sm2
._wD(sm
+ '.unmute(): Unmuting "' + sID
+ '"');
870 return sm2
.sounds
[sID
].unmute();
879 * Unmutes all sounds.
882 this.unmuteAll = function() {
889 * Calls the toggleMute() method of a SMSound object by ID.
891 * @param {string} sID The ID of the sound
892 * @return {SMSound} The SMSound object
895 this.toggleMute = function(sID
) {
900 return sm2
.sounds
[sID
].toggleMute();
905 * Retrieves the memory used by the flash plugin.
907 * @return {number} The amount of memory in use
910 this.getMemoryUse = function() {
915 if (flash
&& fV
!== 8) {
916 ram
= parseInt(flash
._getMemoryUse(), 10);
924 * Undocumented: NOPs soundManager and all SMSound objects.
927 this.disable = function(bNoDisable
) {
929 // destroy all functions
932 if (bNoDisable
=== _undefined
) {
943 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
944 disableObject(sm2
.sounds
[sm2
.soundIDs
[i
]]);
947 // fire "complete", despite fail
948 initComplete(bNoDisable
);
949 event
.remove(window
, 'load', initUserOnload
);
956 * Determines playability of a MIME type, eg. 'audio/mp3'.
959 this.canPlayMIME = function(sMIME
) {
964 result
= html5CanPlay({type
:sMIME
});
967 if (!result
&& needsFlash
) {
968 // if flash 9, test netStream (movieStar) types as well.
969 result
= (sMIME
&& sm2
.ok() ? !!((fV
> 8 ? sMIME
.match(netStreamMimeTypes
) : null) || sMIME
.match(sm2
.mimePattern
)) : null);
977 * Determines playability of a URL based on audio support.
979 * @param {string} sURL The URL to test
980 * @return {boolean} URL playability
983 this.canPlayURL = function(sURL
) {
988 result
= html5CanPlay({url
: sURL
});
991 if (!result
&& needsFlash
) {
992 result
= (sURL
&& sm2
.ok() ? !!(sURL
.match(sm2
.filePattern
)) : null);
1000 * Determines playability of an HTML DOM <a> object (or similar object literal) based on audio support.
1002 * @param {object} oLink an HTML DOM <a> object or object literal including href and/or type attributes
1003 * @return {boolean} URL playability
1006 this.canPlayLink = function(oLink
) {
1008 if (oLink
.type
!== _undefined
&& oLink
.type
) {
1009 if (sm2
.canPlayMIME(oLink
.type
)) {
1014 return sm2
.canPlayURL(oLink
.href
);
1019 * Retrieves a SMSound object by ID.
1021 * @param {string} sID The ID of the sound
1022 * @return {SMSound} The SMSound object
1025 this.getSoundById = function(sID
, _suppressDebug
) {
1028 throw new Error(sm
+ '.getSoundById(): sID is null/_undefined');
1031 var result
= sm2
.sounds
[sID
];
1034 if (!result
&& !_suppressDebug
) {
1035 sm2
._wD('"' + sID
+ '" is an invalid sound ID.', 2);
1044 * Queues a callback for execution when SoundManager has successfully initialized.
1046 * @param {function} oMethod The callback method to fire
1047 * @param {object} oScope Optional: The scope to apply to the callback
1050 this.onready = function(oMethod
, oScope
) {
1052 var sType
= 'onready',
1055 if (typeof oMethod
=== 'function') {
1059 sm2
._wD(str('queue', sType
));
1067 addOnEvent(sType
, oMethod
, oScope
);
1074 throw str('needFunction', sType
);
1083 * Queues a callback for execution when SoundManager has failed to initialize.
1085 * @param {function} oMethod The callback method to fire
1086 * @param {object} oScope Optional: The scope to apply to the callback
1089 this.ontimeout = function(oMethod
, oScope
) {
1091 var sType
= 'ontimeout',
1094 if (typeof oMethod
=== 'function') {
1098 sm2
._wD(str('queue', sType
));
1106 addOnEvent(sType
, oMethod
, oScope
);
1107 processOnEvents({type
:sType
});
1113 throw str('needFunction', sType
);
1122 * Writes console.log()-style debug output to a console or in-browser element.
1123 * Applies when debugMode = true
1125 * @param {string} sText The console message
1126 * @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.
1129 this._writeDebug = function(sText
, sTypeOrObject
) {
1131 // pseudo-private console.log()-style output
1134 var sDID
= 'soundmanager-debug', o
, oItem
;
1136 if (!sm2
.debugMode
) {
1140 if (hasConsole
&& sm2
.useConsole
) {
1141 if (sTypeOrObject
&& typeof sTypeOrObject
=== 'object') {
1142 // object passed; dump to console.
1143 console
.log(sText
, sTypeOrObject
);
1144 } else if (debugLevels
[sTypeOrObject
] !== _undefined
) {
1145 console
[debugLevels
[sTypeOrObject
]](sText
);
1149 if (sm2
.consoleOnly
) {
1160 oItem
= doc
.createElement('div');
1162 if (++wdCount
% 2 === 0) {
1163 oItem
.className
= 'sm2-alt';
1166 if (sTypeOrObject
=== _undefined
) {
1169 sTypeOrObject
= parseInt(sTypeOrObject
, 10);
1172 oItem
.appendChild(doc
.createTextNode(sText
));
1174 if (sTypeOrObject
) {
1175 if (sTypeOrObject
>= 2) {
1176 oItem
.style
.fontWeight
= 'bold';
1178 if (sTypeOrObject
=== 3) {
1179 oItem
.style
.color
= '#ff3333';
1184 // o.appendChild(oItem);
1187 o
.insertBefore(oItem
, o
.firstChild
);
1197 // last-resort debugging option
1198 if (wl
.indexOf('sm2-debug=alert') !== -1) {
1199 this._writeDebug = function(sText
) {
1200 window
.alert(sText
);
1206 this._wD
= this._writeDebug
;
1209 * Provides debug / state information on all SMSound objects.
1212 this._debug = function() {
1216 _wDS('currentObj', 1);
1218 for (i
= 0, j
= sm2
.soundIDs
.length
; i
< j
; i
++) {
1219 sm2
.sounds
[sm2
.soundIDs
[i
]]._debug();
1226 * Restarts and re-initializes the SoundManager instance.
1228 * @param {boolean} resetEvents Optional: When true, removes all registered onready and ontimeout event callbacks.
1229 * @param {boolean} excludeInit Options: When true, does not call beginDelayedInit() (which would restart SM2).
1230 * @return {object} soundManager The soundManager instance.
1233 this.reboot = function(resetEvents
, excludeInit
) {
1235 // reset some (or all) state, and re-init unless otherwise specified.
1238 if (sm2
.soundIDs
.length
) {
1239 sm2
._wD('Destroying ' + sm2
.soundIDs
.length
+ ' SMSound objects...');
1245 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
1246 sm2
.sounds
[sm2
.soundIDs
[i
]].destruct();
1256 oRemovedHTML
= flash
.innerHTML
;
1259 oRemoved
= flash
.parentNode
.removeChild(flash
);
1265 // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.
1267 _wDS('badRemove', 2);
1273 // actually, force recreate of movie.
1275 oRemovedHTML
= oRemoved
= needsFlash
= flash
= null;
1277 sm2
.enabled
= didDCLoaded
= didInit
= waitingForEI
= initPending
= didAppend
= appendSuccess
= disabled
= useGlobalHTML5Audio
= sm2
.swfLoaded
= false;
1283 // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init
1284 for (i
in on_queue
) {
1285 if (on_queue
.hasOwnProperty(i
)) {
1286 for (j
= 0, k
= on_queue
[i
].length
; j
< k
; j
++) {
1287 on_queue
[i
][j
].fired
= false;
1292 // remove all callbacks entirely
1298 sm2
._wD(sm
+ ': Rebooting...');
1302 // reset HTML5 and flash canPlay test results
1310 // reset device-specific HTML/flash mode switches
1312 sm2
.html5Only
= false;
1313 sm2
.ignoreFlash
= false;
1315 window
.setTimeout(function() {
1319 // by default, re-init
1322 sm2
.beginDelayedInit();
1331 this.reset = function() {
1334 * Shuts down and restores the SoundManager instance to its original loaded state, without an explicit reboot. All onready/ontimeout handlers are removed.
1335 * After this call, SM2 may be re-initialized via soundManager.beginDelayedInit().
1336 * @return {object} soundManager The soundManager instance.
1340 return sm2
.reboot(true, true);
1345 * Undocumented: Determines the SM2 flash movie's load progress.
1347 * @return {number or null} Percent loaded, or if invalid/unsupported, null.
1350 this.getMoviePercent = function() {
1353 * Interesting syntax notes...
1354 * Flash/ExternalInterface (ActiveX/NPAPI) bridge methods are not typeof "function" nor instanceof Function, but are still valid.
1355 * Additionally, JSLint dislikes ('PercentLoaded' in flash)-style syntax and recommends hasOwnProperty(), which does not work in this case.
1356 * Furthermore, using (flash && flash.PercentLoaded) causes IE to throw "object doesn't support this property or method".
1357 * Thus, 'in' syntax must be used.
1360 return (flash
&& 'PercentLoaded' in flash
? flash
.PercentLoaded() : null); // Yes, JSLint. See nearby comment in source for explanation.
1365 * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().
1368 this.beginDelayedInit = function() {
1370 windowLoaded
= true;
1373 setTimeout(function() {
1392 * Destroys the SoundManager instance and all SMSound instances.
1395 this.destruct = function() {
1397 sm2
._wD(sm
+ '.destruct()');
1403 * SMSound() (sound object) constructor
1404 * ------------------------------------
1406 * @param {object} oOptions Sound options (id and url are required attributes)
1407 * @return {SMSound} The new SMSound object
1410 SMSound = function(oOptions
) {
1412 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
;
1415 // tracks duration + position (time)
1420 this.id
= oOptions
.id
;
1425 this.url
= oOptions
.url
;
1426 this.options
= mixin(oOptions
);
1428 // per-play-instance-specific options
1429 this.instanceOptions
= this.options
;
1432 this._iO
= this.instanceOptions
;
1434 // assign property defaults
1435 this.pan
= this.options
.pan
;
1436 this.volume
= this.options
.volume
;
1438 // whether or not this object is using HTML5
1439 this.isHTML5
= false;
1441 // internal HTML5 Audio() object reference
1445 * SMSound() public methods
1446 * ------------------------
1452 * Writes SMSound object parameters to debug console
1455 this._debug = function() {
1458 sm2
._wD(s
.id
+ ': Merged options:', s
.options
);
1464 * Begins loading a sound per its *url*.
1466 * @param {object} oOptions Optional: Sound options
1467 * @return {SMSound} The SMSound object
1470 this.load = function(oOptions
) {
1472 var oSound
= null, instanceOptions
;
1474 if (oOptions
!== _undefined
) {
1475 s
._iO
= mixin(oOptions
, s
.options
);
1477 oOptions
= s
.options
;
1479 if (lastURL
&& lastURL
!== s
.url
) {
1490 s
._iO
.url
= parseURL(s
._iO
.url
);
1492 // ensure we're in sync
1493 s
.instanceOptions
= s
._iO
;
1496 instanceOptions
= s
._iO
;
1498 sm2
._wD(s
.id
+ ': load (' + instanceOptions
.url
+ ')');
1500 if (instanceOptions
.url
=== s
.url
&& s
.readyState
!== 0 && s
.readyState
!== 2) {
1502 // if loaded and an onload() exists, fire immediately.
1503 if (s
.readyState
=== 3 && instanceOptions
.onload
) {
1504 // assume success based on truthy duration.
1505 wrapCallback(s
, function() {
1506 instanceOptions
.onload
.apply(s
, [(!!s
.duration
)]);
1512 // reset a few state properties
1519 // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
1521 if (html5OK(instanceOptions
)) {
1523 oSound
= s
._setup_html5(instanceOptions
);
1525 if (!oSound
._called_load
) {
1527 s
._html5_canplay
= false;
1529 // TODO: review called_load / html5_canplay logic
1531 // if url provided directly to load(), assign it here.
1533 if (s
.url
!== instanceOptions
.url
) {
1535 sm2
._wD(_wDS('manURL') + ': ' + instanceOptions
.url
);
1537 s
._a
.src
= instanceOptions
.url
;
1539 // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
1541 // reset position for new URL
1546 // given explicit load call, try to preload.
1548 // early HTML5 implementation (non-standard)
1549 s
._a
.autobuffer
= 'auto';
1552 s
._a
.preload
= 'auto';
1554 s
._a
._called_load
= true;
1556 if (instanceOptions
.autoPlay
) {
1562 sm2
._wD(s
.id
+ ': Ignoring request to load again');
1570 s
._iO
= policyFix(loopFix(instanceOptions
));
1571 // re-assign local shortcut
1572 instanceOptions
= s
._iO
;
1574 flash
._load(s
.id
, instanceOptions
.url
, instanceOptions
.stream
, instanceOptions
.autoPlay
, instanceOptions
.usePolicyFile
);
1576 flash
._load(s
.id
, instanceOptions
.url
, !!(instanceOptions
.stream
), !!(instanceOptions
.autoPlay
), instanceOptions
.loops
||1, !!(instanceOptions
.autoLoad
), instanceOptions
.usePolicyFile
);
1580 debugTS('onload', false);
1581 catchError({type
:'SMSOUND_LOAD_JS_EXCEPTION', fatal
:true});
1586 // after all of this, ensure sound url is up to date.
1587 s
.url
= instanceOptions
.url
;
1594 * Unloads a sound, canceling any open HTTP requests.
1596 * @return {SMSound} The SMSound object
1599 this.unload = function() {
1601 // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL
1602 // Flash 9/AS3: Close stream, preventing further load
1603 // HTML5: Most UAs will use empty URL
1605 if (s
.readyState
!== 0) {
1607 sm2
._wD(s
.id
+ ': unload()');
1612 flash
._unload(s
.id
, emptyURL
);
1614 flash
._unload(s
.id
);
1624 html5Unload(s
._a
, emptyURL
);
1626 // update empty URL, too
1633 // reset load/status flags
1643 * Unloads and destroys a sound.
1646 this.destruct = function(_bFromSM
) {
1648 sm2
._wD(s
.id
+ ': Destruct');
1652 // kill sound within Flash
1653 // Disable the onfailure handler
1654 s
._iO
.onfailure
= null;
1655 flash
._destroySound(s
.id
);
1664 if (!useGlobalHTML5Audio
) {
1665 remove_html5_events();
1667 // break obvious circular reference
1675 // ensure deletion from controller
1676 sm2
.destroySound(s
.id
, true);
1683 * Begins playing a sound.
1685 * @param {object} oOptions Optional: Sound options
1686 * @return {SMSound} The SMSound object
1689 this.play = function(oOptions
, _updatePlayState
) {
1691 var fN
, allowMulti
, a
, onready
, startOK
= true,
1695 fN
= s
.id
+ ': play(): ';
1699 _updatePlayState
= (_updatePlayState
=== _undefined
? true : _updatePlayState
);
1705 // first, use local URL (if specified)
1710 // mix in any options defined at createSound()
1711 s
._iO
= mixin(s
._iO
, s
.options
);
1713 // mix in any options specific to this method
1714 s
._iO
= mixin(oOptions
, s
._iO
);
1716 s
._iO
.url
= parseURL(s
._iO
.url
);
1718 s
.instanceOptions
= s
._iO
;
1721 if (s
._iO
.serverURL
&& !s
.connected
) {
1722 if (!s
.getAutoPlay()) {
1723 sm2
._wD(fN
+' Netstream not connected yet - setting autoPlay');
1724 s
.setAutoPlay(true);
1726 // play will be called in onconnect()
1730 if (html5OK(s
._iO
)) {
1731 s
._setup_html5(s
._iO
);
1732 start_html5_timer();
1735 if (s
.playState
=== 1 && !s
.paused
) {
1736 allowMulti
= s
._iO
.multiShot
;
1738 sm2
._wD(fN
+ 'Already playing (one-shot)', 1);
1741 sm2
._wD(fN
+ 'Already playing (multi-shot)', 1);
1745 if (exit
!== null) {
1749 // edge case: play() with explicit URL parameter
1750 if (oOptions
.url
&& oOptions
.url
!== s
.url
) {
1751 // load using merged options
1757 if (s
.readyState
=== 0) {
1759 sm2
._wD(fN
+ 'Attempting to load');
1761 // try to get this sound playing ASAP
1763 // assign directly because setAutoPlay() increments the instanceCount
1764 s
._iO
.autoPlay
= true;
1767 // iOS needs this when recycling sounds, loading a new URL on an existing object.
1771 // HTML5 hack - re-set instanceOptions?
1772 s
.instanceOptions
= s
._iO
;
1774 } else if (s
.readyState
=== 2) {
1776 sm2
._wD(fN
+ 'Could not load - exiting', 2);
1781 sm2
._wD(fN
+ 'Loading - attempting to play...');
1788 sm2
._wD(fN
.substr(0, fN
.lastIndexOf(':')));
1792 if (exit
!== null) {
1796 if (!s
.isHTML5
&& fV
=== 9 && s
.position
> 0 && s
.position
=== s
.duration
) {
1797 // flash 9 needs a position reset if play() is called while at the end of a sound.
1798 sm2
._wD(fN
+ 'Sound at end, resetting to position:0');
1799 oOptions
.position
= 0;
1803 * Streams will pause when their buffer is full if they are being loaded.
1804 * In this case paused is true, but the song hasn't started playing yet.
1805 * If we just call resume() the onplay() callback will never be called.
1806 * So only call resume() if the position is > 0.
1807 * Another reason is because options like volume won't have been applied yet.
1808 * For normal sounds, just resume.
1811 if (s
.paused
&& s
.position
>= 0 && (!s
._iO
.serverURL
|| s
.position
> 0)) {
1813 // https://gist.github.com/37b17df75cc4d7a90bf6
1814 sm2
._wD(fN
+ 'Resuming from paused state', 1);
1819 s
._iO
= mixin(oOptions
, s
._iO
);
1821 // apply from/to parameters, if they exist (and not using RTMP)
1822 if (s
._iO
.from !== null && s
._iO
.to
!== null && s
.instanceCount
=== 0 && s
.playState
=== 0 && !s
._iO
.serverURL
) {
1824 onready = function() {
1825 // sound "canplay" or onload()
1826 // re-apply from/to to instance options, and start playback
1827 s
._iO
= mixin(oOptions
, s
._iO
);
1831 // HTML5 needs to at least have "canplay" fired before seeking.
1832 if (s
.isHTML5
&& !s
._html5_canplay
) {
1834 // this hasn't been loaded yet. load it first, and then do this again.
1835 sm2
._wD(fN
+ 'Beginning load for from/to case');
1838 // TODO: was _oncanplay. Sounds wrong.
1844 } else if (!s
.isHTML5
&& !s
.loaded
&& (!s
.readyState
|| s
.readyState
!== 2)) {
1846 // to be safe, preload the whole thing in Flash.
1848 sm2
._wD(fN
+ 'Preloading for from/to case');
1858 if (exit
!== null) {
1862 // otherwise, we're ready to go. re-apply local options, and continue
1864 s
._iO
= applyFromTo();
1868 sm2
._wD(fN
+ 'Starting to play');
1870 if (!s
.instanceCount
|| s
._iO
.multiShotEvents
|| (!s
.isHTML5
&& fV
> 8 && !s
.getAutoPlay())) {
1874 // if first play and onposition parameters exist, apply them now
1875 if (s
._iO
.onposition
&& s
.playState
=== 0) {
1876 attachOnPosition(s
);
1882 s
.position
= (s
._iO
.position
!== _undefined
&& !isNaN(s
._iO
.position
) ? s
._iO
.position
: 0);
1885 s
._iO
= policyFix(loopFix(s
._iO
));
1888 if (s
._iO
.onplay
&& _updatePlayState
) {
1889 s
._iO
.onplay
.apply(s
);
1890 onplay_called
= true;
1893 s
.setVolume(s
._iO
.volume
, true);
1894 s
.setPan(s
._iO
.pan
, true);
1898 startOK
= flash
._start(s
.id
, s
._iO
.loops
|| 1, (fV
=== 9 ? s
.position
: s
.position
/ 1000), s
._iO
.multiShot
|| false);
1900 if (fV
=== 9 && !startOK
) {
1901 // edge case: no sound hardware, or 32-channel flash ceiling hit.
1902 // applies only to Flash 9, non-NetStream/MovieStar sounds.
1903 // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
1904 sm2
._wD(fN
+ 'No sound hardware, or 32-sound ceiling hit');
1905 if (s
._iO
.onplayerror
) {
1906 s
._iO
.onplayerror
.apply(s
);
1913 start_html5_timer();
1915 a
= s
._setup_html5();
1917 s
.setPosition(s
._iO
.position
);
1929 // just for convenience
1930 this.start
= this.play
;
1933 * Stops playing a sound (and optionally, all sounds)
1935 * @param {boolean} bAll Optional: Whether to stop all sounds
1936 * @return {SMSound} The SMSound object
1939 this.stop = function(bAll
) {
1941 var instanceOptions
= s
._iO
,
1944 if (s
.playState
=== 1) {
1946 sm2
._wD(s
.id
+ ': stop()');
1948 s
._onbufferchange(0);
1949 s
._resetOnPosition(0);
1956 // remove onPosition listeners, if any
1959 // and "to" position, if set
1960 if (instanceOptions
.to
) {
1961 s
.clearOnPosition(instanceOptions
.to
);
1966 flash
._stop(s
.id
, bAll
);
1968 // hack for netStream: just unload
1969 if (instanceOptions
.serverURL
) {
1977 originalPosition
= s
.position
;
1979 // act like Flash, though
1982 // hack: reflect old position for onstop() (also like Flash)
1983 s
.position
= originalPosition
;
1985 // html5 has no stop()
1986 // NOTE: pausing means iOS requires interaction to resume.
2000 s
.instanceCount
= 0;
2003 if (instanceOptions
.onstop
) {
2004 instanceOptions
.onstop
.apply(s
);
2014 * Undocumented/internal: Sets autoPlay for RTMP.
2016 * @param {boolean} autoPlay state
2019 this.setAutoPlay = function(autoPlay
) {
2021 sm2
._wD(s
.id
+ ': Autoplay turned ' + (autoPlay
? 'on' : 'off'));
2022 s
._iO
.autoPlay
= autoPlay
;
2025 flash
._setAutoPlay(s
.id
, autoPlay
);
2027 // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
2028 if (!s
.instanceCount
&& s
.readyState
=== 1) {
2030 sm2
._wD(s
.id
+ ': Incremented instance count to '+s
.instanceCount
);
2038 * Undocumented/internal: Returns the autoPlay boolean.
2040 * @return {boolean} The current autoPlay value
2043 this.getAutoPlay = function() {
2045 return s
._iO
.autoPlay
;
2050 * Sets the position of a sound.
2052 * @param {number} nMsecOffset Position (milliseconds)
2053 * @return {SMSound} The SMSound object
2056 this.setPosition = function(nMsecOffset
) {
2058 if (nMsecOffset
=== _undefined
) {
2063 position
, position1K
,
2064 // Use the duration from the instance options, if we don't have a track duration yet.
2065 // position >= 0 and <= current available (loaded) duration
2066 offset
= (s
.isHTML5
? Math
.max(nMsecOffset
, 0) : Math
.min(s
.duration
|| s
._iO
.duration
, Math
.max(nMsecOffset
, 0)));
2068 original_pos
= s
.position
;
2069 s
.position
= offset
;
2070 position1K
= s
.position
/1000;
2071 s
._resetOnPosition(s
.position
);
2072 s
._iO
.position
= offset
;
2076 position
= (fV
=== 9 ? s
.position
: position1K
);
2077 if (s
.readyState
&& s
.readyState
!== 2) {
2078 // if paused or not playing, will not resume (by playing)
2079 flash
._setPosition(s
.id
, position
, (s
.paused
|| !s
.playState
), s
._iO
.multiShot
);
2084 // Set the position in the canplay handler if the sound is not ready yet
2085 if (s
._html5_canplay
) {
2086 if (s
._a
.currentTime
!== position1K
) {
2088 * DOM/JS errors/exceptions to watch out for:
2089 * if seek is beyond (loaded?) position, "DOM exception 11"
2090 * "INDEX_SIZE_ERR": DOM exception 1
2092 sm2
._wD(s
.id
+ ': setPosition('+position1K
+')');
2094 s
._a
.currentTime
= position1K
;
2095 if (s
.playState
=== 0 || s
.paused
) {
2096 // allow seek without auto-play/resume
2100 sm2
._wD(s
.id
+ ': setPosition(' + position1K
+ ') failed: ' + e
.message
, 2);
2104 sm2
._wD(s
.id
+ ': setPosition(' + position1K
+ '): Cannot seek yet, sound not ready');
2111 // if paused, refresh UI right away
2122 * Pauses sound playback.
2124 * @return {SMSound} The SMSound object
2127 this.pause = function(_bCallFlash
) {
2129 if (s
.paused
|| (s
.playState
=== 0 && s
.readyState
!== 1)) {
2133 sm2
._wD(s
.id
+ ': pause()');
2137 if (_bCallFlash
|| _bCallFlash
=== _undefined
) {
2138 flash
._pause(s
.id
, s
._iO
.multiShot
);
2141 s
._setup_html5().pause();
2145 if (s
._iO
.onpause
) {
2146 s
._iO
.onpause
.apply(s
);
2154 * Resumes sound playback.
2156 * @return {SMSound} The SMSound object
2160 * When auto-loaded streams pause on buffer full they have a playState of 0.
2161 * We need to make sure that the playState is set to 1 when these streams "resume".
2162 * When a paused stream is resumed, we need to trigger the onplay() callback if it
2163 * hasn't been called already. In this case since the sound is being played for the
2164 * first time, I think it's more appropriate to call onplay() rather than onresume().
2167 this.resume = function() {
2169 var instanceOptions
= s
._iO
;
2175 sm2
._wD(s
.id
+ ': resume()');
2180 if (instanceOptions
.isMovieStar
&& !instanceOptions
.serverURL
) {
2181 // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.
2182 s
.setPosition(s
.position
);
2184 // flash method is toggle-based (pause/resume)
2185 flash
._pause(s
.id
, instanceOptions
.multiShot
);
2187 s
._setup_html5().play();
2188 start_html5_timer();
2191 if (!onplay_called
&& instanceOptions
.onplay
) {
2192 instanceOptions
.onplay
.apply(s
);
2193 onplay_called
= true;
2194 } else if (instanceOptions
.onresume
) {
2195 instanceOptions
.onresume
.apply(s
);
2203 * Toggles sound playback.
2205 * @return {SMSound} The SMSound object
2208 this.togglePause = function() {
2210 sm2
._wD(s
.id
+ ': togglePause()');
2212 if (s
.playState
=== 0) {
2214 position
: (fV
=== 9 && !s
.isHTML5
? s
.position
: s
.position
/ 1000)
2230 * Sets the panning (L-R) effect.
2232 * @param {number} nPan The pan value (-100 to 100)
2233 * @return {SMSound} The SMSound object
2236 this.setPan = function(nPan
, bInstanceOnly
) {
2238 if (nPan
=== _undefined
) {
2242 if (bInstanceOnly
=== _undefined
) {
2243 bInstanceOnly
= false;
2247 flash
._setPan(s
.id
, nPan
);
2248 } // else { no HTML5 pan? }
2252 if (!bInstanceOnly
) {
2254 s
.options
.pan
= nPan
;
2264 * @param {number} nVol The volume value (0 to 100)
2265 * @return {SMSound} The SMSound object
2268 this.setVolume = function(nVol
, _bInstanceOnly
) {
2271 * Note: Setting volume has no effect on iOS "special snowflake" devices.
2272 * Hardware volume control overrides software, and volume
2273 * will always return 1 per Apple docs. (iOS 4 + 5.)
2274 * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html
2277 if (nVol
=== _undefined
) {
2281 if (_bInstanceOnly
=== _undefined
) {
2282 _bInstanceOnly
= false;
2286 flash
._setVolume(s
.id
, (sm2
.muted
&& !s
.muted
) || s
.muted
?0:nVol
);
2289 s
._a
.volume
= Math
.max(0, Math
.min(1, nVol
/100));
2292 s
._iO
.volume
= nVol
;
2294 if (!_bInstanceOnly
) {
2296 s
.options
.volume
= nVol
;
2306 * @return {SMSound} The SMSound object
2309 this.mute = function() {
2314 flash
._setVolume(s
.id
, 0);
2324 * Unmutes the sound.
2326 * @return {SMSound} The SMSound object
2329 this.unmute = function() {
2332 var hasIO
= (s
._iO
.volume
!== _undefined
);
2335 flash
._setVolume(s
.id
, hasIO
?s
._iO
.volume
:s
.options
.volume
);
2345 * Toggles the muted state of a sound.
2347 * @return {SMSound} The SMSound object
2350 this.toggleMute = function() {
2352 return (s
.muted
?s
.unmute():s
.mute());
2357 * Registers a callback to be fired when a sound reaches a given position during playback.
2359 * @param {number} nPosition The position to watch for
2360 * @param {function} oMethod The relevant callback to fire
2361 * @param {object} oScope Optional: The scope to apply the callback to
2362 * @return {SMSound} The SMSound object
2365 this.onPosition = function(nPosition
, oMethod
, oScope
) {
2367 // TODO: basic dupe checking?
2369 onPositionItems
.push({
2370 position
: parseInt(nPosition
, 10),
2372 scope
: (oScope
!== _undefined
? oScope
: s
),
2380 // legacy/backwards-compability: lower-case method name
2381 this.onposition
= this.onPosition
;
2384 * Removes registered callback(s) from a sound, by position and/or callback.
2386 * @param {number} nPosition The position to clear callback(s) for
2387 * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position
2388 * @return {SMSound} The SMSound object
2391 this.clearOnPosition = function(nPosition
, oMethod
) {
2395 nPosition
= parseInt(nPosition
, 10);
2397 if (isNaN(nPosition
)) {
2402 for (i
=0; i
< onPositionItems
.length
; i
++) {
2404 if (nPosition
=== onPositionItems
[i
].position
) {
2405 // remove this item if no method was specified, or, if the method matches
2406 if (!oMethod
|| (oMethod
=== onPositionItems
[i
].method
)) {
2407 if (onPositionItems
[i
].fired
) {
2408 // decrement "fired" counter, too
2411 onPositionItems
.splice(i
, 1);
2419 this._processOnPosition = function() {
2421 var i
, item
, j
= onPositionItems
.length
;
2423 if (!j
|| !s
.playState
|| onPositionFired
>= j
) {
2427 for (i
=j
-1; i
>= 0; i
--) {
2428 item
= onPositionItems
[i
];
2429 if (!item
.fired
&& s
.position
>= item
.position
) {
2432 item
.method
.apply(item
.scope
, [item
.position
]);
2440 this._resetOnPosition = function(nPosition
) {
2442 // reset "fired" for items interested in this position
2443 var i
, item
, j
= onPositionItems
.length
;
2449 for (i
=j
-1; i
>= 0; i
--) {
2450 item
= onPositionItems
[i
];
2451 if (item
.fired
&& nPosition
<= item
.position
) {
2462 * SMSound() private internals
2463 * --------------------------------
2466 applyFromTo = function() {
2468 var instanceOptions
= s
._iO
,
2469 f
= instanceOptions
.from,
2470 t
= instanceOptions
.to
,
2475 // end has been reached.
2476 sm2
._wD(s
.id
+ ': "To" time of ' + t
+ ' reached.');
2479 s
.clearOnPosition(t
, end
);
2481 // stop should clear this, too
2486 start = function() {
2488 sm2
._wD(s
.id
+ ': Playing "from" ' + f
);
2490 // add listener for end
2491 if (t
!== null && !isNaN(t
)) {
2492 s
.onPosition(t
, end
);
2497 if (f
!== null && !isNaN(f
)) {
2499 // apply to instance options, guaranteeing correct start position.
2500 instanceOptions
.position
= f
;
2502 // multiShot timing can't be tracked, so prevent that.
2503 instanceOptions
.multiShot
= false;
2509 // return updated instanceOptions including starting position
2510 return instanceOptions
;
2514 attachOnPosition = function() {
2517 op
= s
._iO
.onposition
;
2519 // attach onposition things, if any, now.
2524 if (op
.hasOwnProperty(item
)) {
2525 s
.onPosition(parseInt(item
, 10), op
[item
]);
2533 detachOnPosition = function() {
2536 op
= s
._iO
.onposition
;
2538 // detach any onposition()-style listeners.
2543 if (op
.hasOwnProperty(item
)) {
2544 s
.clearOnPosition(parseInt(item
, 10));
2552 start_html5_timer = function() {
2560 stop_html5_timer = function() {
2568 resetProperties = function(retainPosition
) {
2570 if (!retainPosition
) {
2571 onPositionItems
= [];
2572 onPositionFired
= 0;
2575 onplay_called
= false;
2579 s
._html5_canplay
= false;
2580 s
.bytesLoaded
= null;
2581 s
.bytesTotal
= null;
2582 s
.duration
= (s
._iO
&& s
._iO
.duration
? s
._iO
.duration
: null);
2583 s
.durationEstimate
= null;
2590 s
.eqData
.right
= [];
2593 s
.isBuffering
= false;
2594 s
.instanceOptions
= {};
2595 s
.instanceCount
= 0;
2599 // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
2625 * Pseudo-private SMSound internals
2626 * --------------------------------
2629 this._onTimer = function(bForce
) {
2632 * HTML5-only _whileplaying() etc.
2633 * called from both HTML5 native events, and polling/interval-based timers
2634 * mimics flash and fires only when time/duration change, so as to be polling-friendly
2637 var duration
, isNew
= false, time
, x
= {};
2639 if (s
._hasTimer
|| bForce
) {
2641 // TODO: May not need to track readyState (1 = loading)
2643 if (s
._a
&& (bForce
|| ((s
.playState
> 0 || s
.readyState
=== 1) && !s
.paused
))) {
2645 duration
= s
._get_html5_duration();
2647 if (duration
!== lastHTML5State
.duration
) {
2649 lastHTML5State
.duration
= duration
;
2650 s
.duration
= duration
;
2655 // TODO: investigate why this goes wack if not set/re-set each time.
2656 s
.durationEstimate
= s
.duration
;
2658 time
= (s
._a
.currentTime
* 1000 || 0);
2660 if (time
!== lastHTML5State
.time
) {
2662 lastHTML5State
.time
= time
;
2667 if (isNew
|| bForce
) {
2669 s
._whileplaying(time
,x
,x
,x
,x
);
2675 // sm2._wD('_onTimer: Warn for "'+s.id+'": '+(!s._a?'Could not find element. ':'')+(s.playState === 0?'playState bad, 0?':'playState = '+s.playState+', OK'));
2687 this._get_html5_duration = function() {
2689 var instanceOptions
= s
._iO
,
2690 // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null
2691 d
= (s
._a
&& s
._a
.duration
? s
._a
.duration
*1000 : (instanceOptions
&& instanceOptions
.duration
? instanceOptions
.duration
: null)),
2692 result
= (d
&& !isNaN(d
) && d
!== Infinity
? d
: null);
2698 this._apply_loop = function(a
, nLoops
) {
2701 * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
2702 * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
2706 if (!a
.loop
&& nLoops
> 1) {
2707 sm2
._wD('Note: Native HTML5 looping is infinite.', 1);
2711 a
.loop
= (nLoops
> 1 ? 'loop' : '');
2715 this._setup_html5 = function(oOptions
) {
2717 var instanceOptions
= mixin(s
._iO
, oOptions
),
2718 a
= useGlobalHTML5Audio
? globalHTML5Audio
: s
._a
,
2719 dURL
= decodeURI(instanceOptions
.url
),
2723 * "First things first, I, Poppa..." (reset the previous state of the old sound, if playing)
2724 * Fixes case with devices that can only play one sound at a time
2725 * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state
2728 if (useGlobalHTML5Audio
) {
2730 if (dURL
=== decodeURI(lastGlobalHTML5URL
)) {
2731 // global HTML5 audio: re-use of URL
2735 } else if (dURL
=== decodeURI(lastURL
)) {
2737 // options URL is the same as the "last" URL, and we used (loaded) it
2746 if (useGlobalHTML5Audio
) {
2748 if (a
._s
&& a
._s
.playState
&& !sameURL
) {
2750 // global HTML5 audio case, and loading a new URL. stop the currently-playing one.
2755 } else if (!useGlobalHTML5Audio
&& dURL
=== decodeURI(lastURL
)) {
2757 // non-global HTML5 reuse case: same url, ignore request
2758 s
._apply_loop(a
, instanceOptions
.loops
);
2768 // don't retain onPosition() stuff with new URL.
2770 resetProperties(false);
2772 // assign new HTML5 URL
2774 a
.src
= instanceOptions
.url
;
2776 s
.url
= instanceOptions
.url
;
2778 lastURL
= instanceOptions
.url
;
2780 lastGlobalHTML5URL
= instanceOptions
.url
;
2782 a
._called_load
= false;
2788 if (instanceOptions
.autoLoad
|| instanceOptions
.autoPlay
) {
2790 s
._a
= new Audio(instanceOptions
.url
);
2794 // null for stupid Opera 9.64 case
2795 s
._a
= (isOpera
&& opera
.version() < 10 ? new Audio(null) : new Audio());
2799 // assign local reference
2802 a
._called_load
= false;
2804 if (useGlobalHTML5Audio
) {
2806 globalHTML5Audio
= a
;
2814 // store a ref on the track
2817 // store a ref on the audio
2822 s
._apply_loop(a
, instanceOptions
.loops
);
2824 if (instanceOptions
.autoLoad
|| instanceOptions
.autoPlay
) {
2830 // early HTML5 implementation (non-standard)
2831 a
.autobuffer
= false;
2833 // standard ('none' is also an option.)
2842 add_html5_events = function() {
2844 if (s
._a
._added_events
) {
2850 function add(oEvt
, oFn
, bCapture
) {
2851 return s
._a
? s
._a
.addEventListener(oEvt
, oFn
, bCapture
||false) : null;
2854 s
._a
._added_events
= true;
2856 for (f
in html5_events
) {
2857 if (html5_events
.hasOwnProperty(f
)) {
2858 add(f
, html5_events
[f
]);
2866 remove_html5_events = function() {
2868 // Remove event listeners
2872 function remove(oEvt
, oFn
, bCapture
) {
2873 return (s
._a
? s
._a
.removeEventListener(oEvt
, oFn
, bCapture
||false) : null);
2876 sm2
._wD(s
.id
+ ': Removing event listeners');
2877 s
._a
._added_events
= false;
2879 for (f
in html5_events
) {
2880 if (html5_events
.hasOwnProperty(f
)) {
2881 remove(f
, html5_events
[f
]);
2888 * Pseudo-private event internals
2889 * ------------------------------
2892 this._onload = function(nSuccess
) {
2895 // check for duration to prevent false positives from flash 8 when loading from cache.
2896 loadOK
= !!nSuccess
|| (!s
.isHTML5
&& fV
=== 8 && s
.duration
);
2900 sm2
._wD(fN
+ (loadOK
? 'onload()' : 'Failed to load? - ' + s
.url
), (loadOK
? 1 : 2));
2901 if (!loadOK
&& !s
.isHTML5
) {
2902 if (sm2
.sandbox
.noRemote
=== true) {
2903 sm2
._wD(fN
+ str('noNet'), 1);
2905 if (sm2
.sandbox
.noLocal
=== true) {
2906 sm2
._wD(fN
+ str('noLocal'), 1);
2912 s
.readyState
= loadOK
?3:2;
2913 s
._onbufferchange(0);
2916 wrapCallback(s
, function() {
2917 s
._iO
.onload
.apply(s
, [loadOK
]);
2925 this._onbufferchange = function(nIsBuffering
) {
2927 if (s
.playState
=== 0) {
2928 // ignore if not playing
2932 if ((nIsBuffering
&& s
.isBuffering
) || (!nIsBuffering
&& !s
.isBuffering
)) {
2936 s
.isBuffering
= (nIsBuffering
=== 1);
2937 if (s
._iO
.onbufferchange
) {
2938 sm2
._wD(s
.id
+ ': Buffer state change: ' + nIsBuffering
);
2939 s
._iO
.onbufferchange
.apply(s
);
2947 * Playback may have stopped due to buffering, or related reason.
2948 * This state can be encountered on iOS < 6 when auto-play is blocked.
2951 this._onsuspend = function() {
2953 if (s
._iO
.onsuspend
) {
2954 sm2
._wD(s
.id
+ ': Playback suspended');
2955 s
._iO
.onsuspend
.apply(s
);
2963 * flash 9/movieStar + RTMP-only method, should fire only once at most
2964 * at this point we just recreate failed sounds rather than trying to reconnect
2967 this._onfailure = function(msg
, level
, code
) {
2970 sm2
._wD(s
.id
+ ': Failures = ' + s
.failures
);
2972 if (s
._iO
.onfailure
&& s
.failures
=== 1) {
2973 s
._iO
.onfailure(s
, msg
, level
, code
);
2975 sm2
._wD(s
.id
+ ': Ignoring failure');
2980 this._onfinish = function() {
2982 // store local copy before it gets trashed...
2983 var io_onfinish
= s
._iO
.onfinish
;
2985 s
._onbufferchange(0);
2986 s
._resetOnPosition(0);
2988 // reset some state items
2989 if (s
.instanceCount
) {
2993 if (!s
.instanceCount
) {
2995 // remove onPosition listeners, if any
2998 // reset instance options
3001 s
.instanceCount
= 0;
3002 s
.instanceOptions
= {};
3006 // reset position, too
3013 if (!s
.instanceCount
|| s
._iO
.multiShotEvents
) {
3014 // fire onfinish for last, or every instance
3016 sm2
._wD(s
.id
+ ': onfinish()');
3017 wrapCallback(s
, function() {
3018 io_onfinish
.apply(s
);
3027 this._whileloading = function(nBytesLoaded
, nBytesTotal
, nDuration
, nBufferLength
) {
3029 var instanceOptions
= s
._iO
;
3031 s
.bytesLoaded
= nBytesLoaded
;
3032 s
.bytesTotal
= nBytesTotal
;
3033 s
.duration
= Math
.floor(nDuration
);
3034 s
.bufferLength
= nBufferLength
;
3036 if (!s
.isHTML5
&& !instanceOptions
.isMovieStar
) {
3038 if (instanceOptions
.duration
) {
3039 // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.
3040 s
.durationEstimate
= (s
.duration
> instanceOptions
.duration
) ? s
.duration
: instanceOptions
.duration
;
3042 s
.durationEstimate
= parseInt((s
.bytesTotal
/ s
.bytesLoaded
) * s
.duration
, 10);
3047 s
.durationEstimate
= s
.duration
;
3051 // for flash, reflect sequential-load-style buffering
3059 // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
3060 if ((s
.readyState
!== 3 || s
.isHTML5
) && instanceOptions
.whileloading
) {
3061 instanceOptions
.whileloading
.apply(s
);
3066 this._whileplaying = function(nPosition
, oPeakData
, oWaveformDataLeft
, oWaveformDataRight
, oEQData
) {
3068 var instanceOptions
= s
._iO
,
3071 if (isNaN(nPosition
) || nPosition
=== null) {
3076 // 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.
3077 s
.position
= Math
.max(0, nPosition
);
3079 s
._processOnPosition();
3081 if (!s
.isHTML5
&& fV
> 8) {
3083 if (instanceOptions
.usePeakData
&& oPeakData
!== _undefined
&& oPeakData
) {
3085 left
: oPeakData
.leftPeak
,
3086 right
: oPeakData
.rightPeak
3090 if (instanceOptions
.useWaveformData
&& oWaveformDataLeft
!== _undefined
&& oWaveformDataLeft
) {
3092 left
: oWaveformDataLeft
.split(','),
3093 right
: oWaveformDataRight
.split(',')
3097 if (instanceOptions
.useEQData
) {
3098 if (oEQData
!== _undefined
&& oEQData
&& oEQData
.leftEQ
) {
3099 eqLeft
= oEQData
.leftEQ
.split(',');
3101 s
.eqData
.left
= eqLeft
;
3102 if (oEQData
.rightEQ
!== _undefined
&& oEQData
.rightEQ
) {
3103 s
.eqData
.right
= oEQData
.rightEQ
.split(',');
3110 if (s
.playState
=== 1) {
3112 // special case/hack: ensure buffering is false if loading from cache (and not yet started)
3113 if (!s
.isHTML5
&& fV
=== 8 && !s
.position
&& s
.isBuffering
) {
3114 s
._onbufferchange(0);
3117 if (instanceOptions
.whileplaying
) {
3118 // flash may call after actual finish
3119 instanceOptions
.whileplaying
.apply(s
);
3128 this._oncaptiondata = function(oData
) {
3131 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
3133 * @param {object} oData
3136 sm2
._wD(s
.id
+ ': Caption data received.');
3138 s
.captiondata
= oData
;
3140 if (s
._iO
.oncaptiondata
) {
3141 s
._iO
.oncaptiondata
.apply(s
, [oData
]);
3146 this._onmetadata = function(oMDProps
, oMDData
) {
3149 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
3150 * RTMP may include song title, MovieStar content may include encoding info
3152 * @param {array} oMDProps (names)
3153 * @param {array} oMDData (values)
3156 sm2
._wD(s
.id
+ ': Metadata received.');
3158 var oData
= {}, i
, j
;
3160 for (i
= 0, j
= oMDProps
.length
; i
< j
; i
++) {
3161 oData
[oMDProps
[i
]] = oMDData
[i
];
3165 if (s
._iO
.onmetadata
) {
3166 s
._iO
.onmetadata
.apply(s
);
3171 this._onid3 = function(oID3Props
, oID3Data
) {
3174 * internal: flash 8 + flash 9 ID3 feature
3175 * may include artist, song title etc.
3177 * @param {array} oID3Props (names)
3178 * @param {array} oID3Data (values)
3181 sm2
._wD(s
.id
+ ': ID3 data received.');
3183 var oData
= [], i
, j
;
3185 for (i
= 0, j
= oID3Props
.length
; i
< j
; i
++) {
3186 oData
[oID3Props
[i
]] = oID3Data
[i
];
3188 s
.id3
= mixin(s
.id3
, oData
);
3191 s
._iO
.onid3
.apply(s
);
3198 this._onconnect = function(bSuccess
) {
3200 bSuccess
= (bSuccess
=== 1);
3201 sm2
._wD(s
.id
+ ': ' + (bSuccess
? 'Connected.' : 'Failed to connect? - ' + s
.url
), (bSuccess
? 1 : 2));
3202 s
.connected
= bSuccess
;
3208 if (idCheck(s
.id
)) {
3209 if (s
.getAutoPlay()) {
3210 // only update the play state if auto playing
3211 s
.play(_undefined
, s
.getAutoPlay());
3212 } else if (s
._iO
.autoLoad
) {
3217 if (s
._iO
.onconnect
) {
3218 s
._iO
.onconnect
.apply(s
, [bSuccess
]);
3225 this._ondataerror = function(sError
) {
3227 // flash 9 wave/eq data handler
3228 // hack: called at start, and end from flash at/after onfinish()
3229 if (s
.playState
> 0) {
3230 sm2
._wD(s
.id
+ ': Data error: ' + sError
);
3231 if (s
._iO
.ondataerror
) {
3232 s
._iO
.ondataerror
.apply(s
);
3245 * Private SoundManager internals
3246 * ------------------------------
3249 getDocument = function() {
3251 return (doc
.body
|| doc
._docElement
|| doc
.getElementsByTagName('div')[0]);
3255 id = function(sID
) {
3257 return doc
.getElementById(sID
);
3261 mixin = function(oMain
, oAdd
) {
3263 // non-destructive merge
3264 var o1
= (oMain
|| {}), o2
, o
;
3266 // if unspecified, o2 is the default options object
3267 o2
= (oAdd
=== _undefined
? sm2
.defaultOptions
: oAdd
);
3271 if (o2
.hasOwnProperty(o
) && o1
[o
] === _undefined
) {
3273 if (typeof o2
[o
] !== 'object' || o2
[o
] === null) {
3280 // recurse through o2
3281 o1
[o
] = mixin(o1
[o
], o2
[o
]);
3293 wrapCallback = function(oSound
, callback
) {
3296 * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue
3297 * 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).
3298 * 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.
3299 * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.
3300 * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player
3302 if (!oSound
.isHTML5
&& fV
=== 8) {
3303 window
.setTimeout(callback
, 0);
3310 // additional soundManager properties that soundManager.setup() will accept
3315 'defaultOptions': 1,
3317 'movieStarOptions': 1
3320 assign = function(o
, oParent
) {
3323 * recursive assignment of properties, soundManager.setup() helper
3324 * allows property assignment based on whitelist
3329 hasParent
= (oParent
!== _undefined
),
3330 setupOptions
= sm2
.setupOptions
,
3331 bonusOptions
= extraOptions
;
3335 // if soundManager.setup() called, show accepted parameters.
3337 if (o
=== _undefined
) {
3341 for (i
in setupOptions
) {
3343 if (setupOptions
.hasOwnProperty(i
)) {
3349 for (i
in bonusOptions
) {
3351 if (bonusOptions
.hasOwnProperty(i
)) {
3353 if (typeof sm2
[i
] === 'object') {
3355 result
.push(i
+': {...}');
3357 } else if (sm2
[i
] instanceof Function
) {
3359 result
.push(i
+': function() {...}');
3371 sm2
._wD(str('setup', result
.join(', ')));
3381 if (o
.hasOwnProperty(i
)) {
3383 // if not an {object} we want to recurse through...
3385 if (typeof o
[i
] !== 'object' || o
[i
] === null || o
[i
] instanceof Array
|| o
[i
] instanceof RegExp
) {
3387 // check "allowed" options
3389 if (hasParent
&& bonusOptions
[oParent
] !== _undefined
) {
3391 // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
3392 sm2
[oParent
][i
] = o
[i
];
3394 } else if (setupOptions
[i
] !== _undefined
) {
3396 // special case: assign to setupOptions object, which soundManager property references
3397 sm2
.setupOptions
[i
] = o
[i
];
3399 // assign directly to soundManager, too
3402 } else if (bonusOptions
[i
] === _undefined
) {
3404 // invalid or disallowed parameter. complain.
3405 complain(str((sm2
[i
] === _undefined
? 'setupUndef' : 'setupError'), i
), 2);
3412 * valid extraOptions (bonusOptions) parameter.
3413 * is it a method, like onready/ontimeout? call it.
3414 * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
3417 if (sm2
[i
] instanceof Function
) {
3419 sm2
[i
].apply(sm2
, (o
[i
] instanceof Array
? o
[i
] : [o
[i
]]));
3423 // good old-fashioned direct assignment
3432 // recursion case, eg., { defaultOptions: { ... } }
3434 if (bonusOptions
[i
] === _undefined
) {
3436 // invalid or disallowed parameter. complain.
3437 complain(str((sm2
[i
] === _undefined
? 'setupUndef' : 'setupError'), i
), 2);
3443 // recurse through object
3444 return assign(o
[i
], i
);
3458 function preferFlashCheck(kind
) {
3460 // whether flash should play a given type
3461 return (sm2
.preferFlash
&& hasFlash
&& !sm2
.ignoreFlash
&& (sm2
.flash
[kind
] !== _undefined
&& sm2
.flash
[kind
]));
3466 * Internal DOM2-level event helpers
3467 * ---------------------------------
3470 event
= (function() {
3472 // normalize event methods
3473 var old
= (window
.attachEvent
),
3475 add
: (old
?'attachEvent':'addEventListener'),
3476 remove
: (old
?'detachEvent':'removeEventListener')
3479 // normalize "on" event prefix, optional capture argument
3480 function getArgs(oArgs
) {
3482 var args
= slice
.call(oArgs
),
3487 args
[1] = 'on' + args
[1];
3492 } else if (len
=== 3) {
3500 function apply(args
, sType
) {
3502 // normalize and call the event method, with the proper arguments
3503 var element
= args
.shift(),
3504 method
= [evt
[sType
]];
3507 // old IE can't do apply().
3508 element
[method
](args
[0], args
[1]);
3510 element
[method
].apply(element
, args
);
3517 apply(getArgs(arguments
), 'add');
3523 apply(getArgs(arguments
), 'remove');
3535 * Internal HTML5 event handling
3536 * -----------------------------
3539 function html5_event(oFn
) {
3541 // wrap html5 event handlers so we don't call them on destroyed and/or unloaded sounds
3543 return function(e
) {
3551 sm2
._wD(s
.id
+ ': Ignoring ' + e
.type
);
3553 sm2
._wD(h5
+ 'Ignoring ' + e
.type
);
3558 result
= oFn
.call(this, e
);
3569 // HTML5 event-name-to-handler map
3571 abort
: html5_event(function() {
3573 sm2
._wD(this._s
.id
+ ': abort');
3577 // enough has loaded to play
3579 canplay
: html5_event(function() {
3584 if (s
._html5_canplay
) {
3585 // this event has already fired. ignore.
3589 s
._html5_canplay
= true;
3590 sm2
._wD(s
.id
+ ': canplay');
3591 s
._onbufferchange(0);
3593 // position according to instance options
3594 position1K
= (s
._iO
.position
!== _undefined
&& !isNaN(s
._iO
.position
)?s
._iO
.position
/1000:null);
3596 // set the position if position was set before the sound loaded
3597 if (s
.position
&& this.currentTime
!== position1K
) {
3598 sm2
._wD(s
.id
+ ': canplay: Setting position to ' + position1K
);
3600 this.currentTime
= position1K
;
3602 sm2
._wD(s
.id
+ ': canplay: Setting position of ' + position1K
+ ' failed: ' + ee
.message
, 2);
3606 // hack for HTML5 from/to case
3607 if (s
._iO
._oncanplay
) {
3613 canplaythrough
: html5_event(function() {
3618 s
._onbufferchange(0);
3619 s
._whileloading(s
.bytesLoaded
, s
.bytesTotal
, s
._get_html5_duration());
3625 // TODO: Reserved for potential use
3627 emptied: html5_event(function() {
3629 sm2._wD(this._s.id + ': emptied');
3634 ended
: html5_event(function() {
3638 sm2
._wD(s
.id
+ ': ended');
3644 error
: html5_event(function() {
3646 sm2
._wD(this._s
.id
+ ': HTML5 error, code ' + this.error
.code
);
3647 // call load with error state?
3648 this._s
._onload(false);
3652 loadeddata
: html5_event(function() {
3656 sm2
._wD(s
.id
+ ': loadeddata');
3658 // safari seems to nicely report progress events, eventually totalling 100%
3659 if (!s
._loaded
&& !isSafari
) {
3660 s
.duration
= s
._get_html5_duration();
3665 loadedmetadata
: html5_event(function() {
3667 sm2
._wD(this._s
.id
+ ': loadedmetadata');
3671 loadstart
: html5_event(function() {
3673 sm2
._wD(this._s
.id
+ ': loadstart');
3674 // assume buffering at first
3675 this._s
._onbufferchange(1);
3679 play
: html5_event(function() {
3681 sm2
._wD(this._s
.id
+ ': play()');
3682 // once play starts, no buffering
3683 this._s
._onbufferchange(0);
3687 playing
: html5_event(function() {
3689 sm2
._wD(this._s
.id
+ ': playing');
3690 // once play starts, no buffering
3691 this._s
._onbufferchange(0);
3695 progress
: html5_event(function(e
) {
3697 // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
3700 i
, j
, str
, buffered
= 0,
3701 isProgress
= (e
.type
=== 'progress'),
3702 ranges
= e
.target
.buffered
,
3703 // firefox 3.6 implements e.loaded/total (bytes)
3704 loaded
= (e
.loaded
||0),
3705 total
= (e
.total
||1),
3706 // HTML5 returns msec. SM2 API uses seconds for setPosition() etc., whether Flash or HTML5.
3709 // reset the "buffered" (loaded byte ranges) array
3712 if (ranges
&& ranges
.length
) {
3714 // if loaded is 0, try TimeRanges implementation as % of load
3715 // https://developer.mozilla.org/en/DOM/TimeRanges
3717 // re-build "buffered" array
3718 for (i
=0, j
=ranges
.length
; i
<j
; i
++) {
3720 'start': ranges
.start(i
) * scale
,
3721 'end': ranges
.end(i
) * scale
3725 // use the last value locally
3726 buffered
= (ranges
.end(0) - ranges
.start(0)) * scale
;
3728 // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
3729 loaded
= buffered
/(e
.target
.duration
*scale
);
3732 if (isProgress
&& ranges
.length
> 1) {
3735 for (i
=0; i
<j
; i
++) {
3736 str
.push(e
.target
.buffered
.start(i
)*scale
+'-'+ e
.target
.buffered
.end(i
)*scale
);
3738 sm2
._wD(this._s
.id
+ ': progress, timeRanges: ' + str
.join(', '));
3741 if (isProgress
&& !isNaN(loaded
)) {
3742 sm2
._wD(this._s
.id
+ ': progress, ' + Math
.floor(loaded
*100) + '% loaded');
3748 if (!isNaN(loaded
)) {
3750 // if progress, likely not buffering
3751 s
._onbufferchange(0);
3752 // TODO: prevent calls with duplicate values.
3753 s
._whileloading(loaded
, total
, s
._get_html5_duration());
3754 if (loaded
&& total
&& loaded
=== total
) {
3755 // in case "onload" doesn't fire (eg. gecko 1.9.2)
3756 html5_events
.canplaythrough
.call(this, e
);
3763 ratechange
: html5_event(function() {
3765 sm2
._wD(this._s
.id
+ ': ratechange');
3769 suspend
: html5_event(function(e
) {
3771 // download paused/stopped, may have finished (eg. onload)
3774 sm2
._wD(this._s
.id
+ ': suspend');
3775 html5_events
.progress
.call(this, e
);
3780 stalled
: html5_event(function() {
3782 sm2
._wD(this._s
.id
+ ': stalled');
3786 timeupdate
: html5_event(function() {
3792 waiting
: html5_event(function() {
3796 // see also: seeking
3797 sm2
._wD(this._s
.id
+ ': waiting');
3799 // playback faster than download rate, etc.
3800 s
._onbufferchange(1);
3806 html5OK = function(iO
) {
3808 // playability test based on URL or MIME type
3812 if (iO
.serverURL
|| (iO
.type
&& preferFlashCheck(iO
.type
))) {
3814 // RTMP, or preferring flash
3819 // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
3820 result
= ((iO
.type
? html5CanPlay({type
:iO
.type
}) : html5CanPlay({url
:iO
.url
}) || sm2
.html5Only
));
3828 html5Unload = function(oAudio
, url
) {
3831 * Internal method: Unload media, and cancel any current/pending network requests.
3832 * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
3833 * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
3834 * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
3835 * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
3840 // Firefox likes '' for unload (used to work?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
3843 // reset some state, too
3844 oAudio
._called_load
= false;
3848 if (useGlobalHTML5Audio
) {
3850 // ensure URL state is trashed, also
3851 lastGlobalHTML5URL
= null;
3857 html5CanPlay = function(o
) {
3860 * Try to find MIME, test and return truthiness
3862 * url: '/path/to/an.mp3',
3867 if (!sm2
.useHTML5Audio
|| !sm2
.hasHTML5
) {
3871 var url
= (o
.url
|| null),
3872 mime
= (o
.type
|| null),
3873 aF
= sm2
.audioFormats
,
3879 // account for known cases like audio/mp3
3881 if (mime
&& sm2
.html5
[mime
] !== _undefined
) {
3882 return (sm2
.html5
[mime
] && !preferFlashCheck(mime
));
3888 if (aF
.hasOwnProperty(item
)) {
3889 html5Ext
.push(item
);
3890 if (aF
[item
].related
) {
3891 html5Ext
= html5Ext
.concat(aF
[item
].related
);
3895 html5Ext
= new RegExp('\\.('+html5Ext
.join('|')+')(\\?.*)?$','i');
3898 // TODO: Strip URL queries, etc.
3899 fileExt
= (url
? url
.toLowerCase().match(html5Ext
) : null);
3901 if (!fileExt
|| !fileExt
.length
) {
3905 // audio/mp3 -> mp3, result should be known
3906 offset
= mime
.indexOf(';');
3907 // strip "audio/X; codecs..."
3908 fileExt
= (offset
!== -1?mime
.substr(0,offset
):mime
).substr(6);
3911 // match the raw extension name - "mp3", for example
3912 fileExt
= fileExt
[1];
3915 if (fileExt
&& sm2
.html5
[fileExt
] !== _undefined
) {
3917 result
= (sm2
.html5
[fileExt
] && !preferFlashCheck(fileExt
));
3919 mime
= 'audio/'+fileExt
;
3920 result
= sm2
.html5
.canPlayType({type
:mime
});
3921 sm2
.html5
[fileExt
] = result
;
3922 // sm2._wD('canPlayType, found result: ' + result);
3923 result
= (result
&& sm2
.html5
[mime
] && !preferFlashCheck(mime
));
3930 testHTML5 = function() {
3933 * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on
3934 * assigns results to html5[] and flash[].
3937 if (!sm2
.useHTML5Audio
|| !sm2
.hasHTML5
) {
3941 // 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. :/
3942 var a
= (Audio
!== _undefined
? (isOpera
&& opera
.version() < 10 ? new Audio(null) : new Audio()) : null),
3943 item
, lookup
, support
= {}, aF
, i
;
3951 if (!a
|| typeof a
.canPlayType
!== 'function') {
3955 if (m
instanceof Array
) {
3956 // iterate through all mime types, return any successes
3957 for (i
=0, j
=m
.length
; i
<j
; i
++) {
3958 if (sm2
.html5
[m
[i
]] || a
.canPlayType(m
[i
]).match(sm2
.html5Test
)) {
3960 sm2
.html5
[m
[i
]] = true;
3961 // note flash support, too
3962 sm2
.flash
[m
[i
]] = !!(m
[i
].match(flashMIME
));
3967 canPlay
= (a
&& typeof a
.canPlayType
=== 'function' ? a
.canPlayType(m
) : false);
3968 result
= !!(canPlay
&& (canPlay
.match(sm2
.html5Test
)));
3975 // test all registered formats + codecs
3977 aF
= sm2
.audioFormats
;
3981 if (aF
.hasOwnProperty(item
)) {
3983 lookup
= 'audio/' + item
;
3985 support
[item
] = cp(aF
[item
].type
);
3987 // write back generic type too, eg. audio/mp3
3988 support
[lookup
] = support
[item
];
3991 if (item
.match(flashMIME
)) {
3993 sm2
.flash
[item
] = true;
3994 sm2
.flash
[lookup
] = true;
3998 sm2
.flash
[item
] = false;
3999 sm2
.flash
[lookup
] = false;
4003 // assign result to related formats, too
4005 if (aF
[item
] && aF
[item
].related
) {
4007 for (i
=aF
[item
].related
.length
-1; i
>= 0; i
--) {
4010 support
['audio/'+aF
[item
].related
[i
]] = support
[item
];
4011 sm2
.html5
[aF
[item
].related
[i
]] = support
[item
];
4012 sm2
.flash
[aF
[item
].related
[i
]] = support
[item
];
4022 support
.canPlayType
= (a
?cp
:null);
4023 sm2
.html5
= mixin(sm2
.html5
, support
);
4032 notReady
: 'Unavailable - wait until onready() has fired.',
4033 notOK
: 'Audio support is not available.',
4034 domError
: sm
+ 'exception caught while appending SWF to DOM.',
4035 spcWmode
: 'Removing wmode, preventing known SWF loading issue(s)',
4036 swf404
: smc
+ 'Verify that %s is a valid path.',
4037 tryDebug
: 'Try ' + sm
+ '.debugFlash = true for more security details (output goes to SWF.)',
4038 checkSWF
: 'See SWF output for more debug info.',
4039 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/',
4040 waitFocus
: smc
+ 'Special case: Waiting for SWF to load with window focus...',
4041 waitForever
: smc
+ 'Waiting indefinitely for Flash (will recover if unblocked)...',
4042 waitSWF
: smc
+ 'Waiting for 100% SWF load...',
4043 needFunction
: smc
+ 'Function object expected for %s',
4044 badID
: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',
4045 currentObj
: smc
+ '_debug(): Current sound objects',
4046 waitOnload
: smc
+ 'Waiting for window.onload()',
4047 docLoaded
: smc
+ 'Document already loaded',
4048 onload
: smc
+ 'initComplete(): calling soundManager.onload()',
4049 onloadOK
: sm
+ '.onload() complete',
4050 didInit
: smc
+ 'init(): Already called?',
4051 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',
4052 badRemove
: smc
+ 'Failed to remove Flash node.',
4053 shutdown
: sm
+ '.disable(): Shutting down',
4054 queue
: smc
+ 'Queueing %s handler',
4055 smError
: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
4056 fbTimeout
: 'No flash response, applying .'+swfCSS
.swfTimedout
+' CSS...',
4057 fbLoaded
: 'Flash loaded',
4058 flRemoved
: smc
+ 'Flash movie removed.',
4059 fbHandler
: smc
+ 'flashBlockHandler()',
4060 manURL
: 'SMSound.load(): Using manually-assigned URL',
4061 onURL
: sm
+ '.load(): current URL already assigned.',
4062 badFV
: sm
+ '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',
4063 as2loop
: 'Note: Setting stream:false so looping can work (flash 8 limitation)',
4064 noNSLoop
: 'Note: Looping not implemented for MovieStar formats',
4065 needfl9
: 'Note: Switching to flash 9, required for MP4 formats.',
4066 mfTimeout
: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
4067 needFlash
: smc
+ 'Fatal error: Flash is needed to play some required formats, but is not available.',
4068 gotFocus
: smc
+ 'Got window focus.',
4069 policy
: 'Enabling usePolicyFile for data access',
4070 setup
: sm
+ '.setup(): allowed parameters: %s',
4071 setupError
: sm
+ '.setup(): "%s" cannot be assigned with this method.',
4072 setupUndef
: sm
+ '.setup(): Could not find option "%s"',
4073 setupLate
: sm
+ '.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().',
4074 noURL
: smc
+ 'Flash URL required. Call soundManager.setup({url:...}) to get started.',
4075 sm2Loaded
: 'SoundManager 2: Ready.',
4076 reset
: sm
+ '.reset(): Removing event callbacks',
4077 mobileUA
: 'Mobile UA detected, preferring HTML5 by default.',
4078 globalHTML5
: 'Using singleton HTML5 Audio() pattern for this device.'
4085 // internal string replace helper.
4086 // arguments: o [,items to replace]
4089 // real array, please
4090 var args
= slice
.call(arguments
),
4095 str
= (strings
&& strings
[o
]?strings
[o
]:''), i
, j
;
4096 if (str
&& args
&& args
.length
) {
4097 for (i
= 0, j
= args
.length
; i
< j
; i
++) {
4098 str
= str
.replace('%s', args
[i
]);
4107 loopFix = function(sOpt
) {
4109 // flash 8 requires stream = false for looping to work
4110 if (fV
=== 8 && sOpt
.loops
> 1 && sOpt
.stream
) {
4112 sOpt
.stream
= false;
4119 policyFix = function(sOpt
, sPre
) {
4121 if (sOpt
&& !sOpt
.usePolicyFile
&& (sOpt
.onid3
|| sOpt
.usePeakData
|| sOpt
.useWaveformData
|| sOpt
.useEQData
)) {
4122 sm2
._wD((sPre
|| '') + str('policy'));
4123 sOpt
.usePolicyFile
= true;
4130 complain = function(sMsg
) {
4133 if (console
!== _undefined
&& console
.warn
!== _undefined
) {
4142 doNothing = function() {
4148 disableObject = function(o
) {
4153 if (o
.hasOwnProperty(oProp
) && typeof o
[oProp
] === 'function') {
4154 o
[oProp
] = doNothing
;
4162 failSafely = function(bNoDisable
) {
4164 // general failure exception handler
4166 if (bNoDisable
=== _undefined
) {
4170 if (disabled
|| bNoDisable
) {
4171 sm2
.disable(bNoDisable
);
4176 normalizeMovieURL = function(smURL
) {
4178 var urlParams
= null, url
;
4181 if (smURL
.match(/\.swf(\?.*)?$/i)) {
4182 urlParams
= smURL
.substr(smURL
.toLowerCase().lastIndexOf('.swf?') + 4);
4184 // assume user knows what they're doing
4187 } else if (smURL
.lastIndexOf('/') !== smURL
.length
- 1) {
4188 // append trailing slash, if needed
4193 url
= (smURL
&& smURL
.lastIndexOf('/') !== - 1 ? smURL
.substr(0, smURL
.lastIndexOf('/') + 1) : './') + sm2
.movieURL
;
4195 if (sm2
.noSWFCache
) {
4196 url
+= ('?ts=' + new Date().getTime());
4203 setVersionInfo = function() {
4205 // short-hand for internal use
4207 fV
= parseInt(sm2
.flashVersion
, 10);
4209 if (fV
!== 8 && fV
!== 9) {
4210 sm2
._wD(str('badFV', fV
, defaultFlashVersion
));
4211 sm2
.flashVersion
= fV
= defaultFlashVersion
;
4214 // debug flash movie, if applicable
4216 var isDebug
= (sm2
.debugMode
|| sm2
.debugFlash
?'_debug.swf':'.swf');
4218 if (sm2
.useHTML5Audio
&& !sm2
.html5Only
&& sm2
.audioFormats
.mp4
.required
&& fV
< 9) {
4219 sm2
._wD(str('needfl9'));
4220 sm2
.flashVersion
= fV
= 9;
4223 sm2
.version
= sm2
.versionNumber
+ (sm2
.html5Only
?' (HTML5-only mode)':(fV
=== 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));
4225 // set up default options
4227 // +flash 9 base options
4228 sm2
.defaultOptions
= mixin(sm2
.defaultOptions
, sm2
.flash9Options
);
4229 sm2
.features
.buffering
= true;
4230 // +moviestar support
4231 sm2
.defaultOptions
= mixin(sm2
.defaultOptions
, sm2
.movieStarOptions
);
4232 sm2
.filePatterns
.flash9
= new RegExp('\\.(mp3|' + netStreamTypes
.join('|') + ')(\\?.*)?$', 'i');
4233 sm2
.features
.movieStar
= true;
4235 sm2
.features
.movieStar
= false;
4238 // regExp for flash canPlay(), etc.
4239 sm2
.filePattern
= sm2
.filePatterns
[(fV
!== 8?'flash9':'flash8')];
4241 // if applicable, use _debug versions of SWFs
4242 sm2
.movieURL
= (fV
=== 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug
);
4244 sm2
.features
.peakData
= sm2
.features
.waveformData
= sm2
.features
.eqData
= (fV
> 8);
4248 setPolling = function(bPolling
, bHighPerformance
) {
4254 flash
._setPolling(bPolling
, bHighPerformance
);
4258 initDebug = function() {
4260 // starts debug mode, creating output <div> for UAs without console object
4262 // allow force of debug mode via URL
4263 if (sm2
.debugURLParam
.test(wl
)) {
4264 sm2
.debugMode
= true;
4268 if (id(sm2
.debugID
)) {
4272 var oD
, oDebug
, oTarget
, oToggle
, tmp
;
4274 if (sm2
.debugMode
&& !id(sm2
.debugID
) && (!hasConsole
|| !sm2
.useConsole
|| !sm2
.consoleOnly
)) {
4276 oD
= doc
.createElement('div');
4277 oD
.id
= sm2
.debugID
+ '-toggle';
4280 'position': 'fixed',
4285 'lineHeight': '1.2em',
4287 'textAlign': 'center',
4288 'border': '1px solid #999',
4289 'cursor': 'pointer',
4290 'background': '#fff',
4295 oD
.appendChild(doc
.createTextNode('-'));
4296 oD
.onclick
= toggleDebug
;
4297 oD
.title
= 'Toggle SM2 debug console';
4299 if (ua
.match(/msie 6/i)) {
4300 oD
.style
.position
= 'absolute';
4301 oD
.style
.cursor
= 'hand';
4304 for (tmp
in oToggle
) {
4305 if (oToggle
.hasOwnProperty(tmp
)) {
4306 oD
.style
[tmp
] = oToggle
[tmp
];
4310 oDebug
= doc
.createElement('div');
4311 oDebug
.id
= sm2
.debugID
;
4312 oDebug
.style
.display
= (sm2
.debugMode
?'block':'none');
4314 if (sm2
.debugMode
&& !id(oD
.id
)) {
4316 oTarget
= getDocument();
4317 oTarget
.appendChild(oD
);
4319 throw new Error(str('domError')+' \n'+e2
.toString());
4321 oTarget
.appendChild(oDebug
);
4331 idCheck
= this.getSoundById
;
4334 _wDS = function(o
, errorLevel
) {
4336 return (!o
? '' : sm2
._wD(str(o
), errorLevel
));
4340 toggleDebug = function() {
4342 var o
= id(sm2
.debugID
),
4343 oT
= id(sm2
.debugID
+ '-toggle');
4352 o
.style
.display
= 'none';
4355 o
.style
.display
= 'block';
4358 debugOpen
= !debugOpen
;
4362 debugTS = function(sEventType
, bSuccess
, sMessage
) {
4364 // troubleshooter debug hooks
4366 if (window
.sm2Debugger
!== _undefined
) {
4368 sm2Debugger
.handleEvent(sEventType
, bSuccess
, sMessage
);
4379 getSWFCSS = function() {
4383 if (sm2
.debugMode
) {
4384 css
.push(swfCSS
.sm2Debug
);
4387 if (sm2
.debugFlash
) {
4388 css
.push(swfCSS
.flashDebug
);
4391 if (sm2
.useHighPerformance
) {
4392 css
.push(swfCSS
.highPerf
);
4395 return css
.join(' ');
4399 flashBlockHandler = function() {
4401 // *possible* flash block situation.
4403 var name
= str('fbHandler'),
4404 p
= sm2
.getMoviePercent(),
4406 error
= {type
:'FLASHBLOCK'};
4408 if (sm2
.html5Only
) {
4415 // make the movie more visible, so user can fix
4416 sm2
.oMC
.className
= getSWFCSS() + ' ' + css
.swfDefault
+ ' ' + (p
=== null?css
.swfTimedout
:css
.swfError
);
4417 sm2
._wD(name
+ ': ' + str('fbTimeout') + (p
? ' (' + str('fbLoaded') + ')' : ''));
4420 sm2
.didFlashBlock
= true;
4422 // fire onready(), complain lightly
4423 processOnEvents({type
:'ontimeout', ignoreInit
:true, error
:error
});
4428 // SM2 loaded OK (or recovered)
4431 if (sm2
.didFlashBlock
) {
4432 sm2
._wD(name
+ ': Unblocked');
4437 sm2
.oMC
.className
= [getSWFCSS(), css
.swfDefault
, css
.swfLoaded
+ (sm2
.didFlashBlock
?' '+css
.swfUnblocked
:'')].join(' ');
4444 addOnEvent = function(sType
, oMethod
, oScope
) {
4446 if (on_queue
[sType
] === _undefined
) {
4447 on_queue
[sType
] = [];
4450 on_queue
[sType
].push({
4452 'scope': (oScope
|| null),
4458 processOnEvents = function(oOptions
) {
4460 // if unspecified, assume OK/error
4464 type
: (sm2
.ok() ? 'onready' : 'ontimeout')
4468 if (!didInit
&& oOptions
&& !oOptions
.ignoreInit
) {
4473 if (oOptions
.type
=== 'ontimeout' && (sm2
.ok() || (disabled
&& !oOptions
.ignoreInit
))) {
4479 success
: (oOptions
&& oOptions
.ignoreInit
?sm2
.ok():!disabled
)
4482 // queue specified by type, or none
4483 srcQueue
= (oOptions
&& oOptions
.type
?on_queue
[oOptions
.type
]||[]:[]),
4487 canRetry
= (needsFlash
&& !sm2
.ok());
4489 if (oOptions
.error
) {
4490 args
[0].error
= oOptions
.error
;
4493 for (i
= 0, j
= srcQueue
.length
; i
< j
; i
++) {
4494 if (srcQueue
[i
].fired
!== true) {
4495 queue
.push(srcQueue
[i
]);
4500 // sm2._wD(sm + ': Firing ' + queue.length + ' ' + oOptions.type + '() item' + (queue.length === 1 ? '' : 's'));
4501 for (i
= 0, j
= queue
.length
; i
< j
; i
++) {
4502 if (queue
[i
].scope
) {
4503 queue
[i
].method
.apply(queue
[i
].scope
, args
);
4505 queue
[i
].method
.apply(this, args
);
4508 // useFlashBlock and SWF timeout case doesn't count here.
4509 queue
[i
].fired
= true;
4518 initUserOnload = function() {
4520 window
.setTimeout(function() {
4522 if (sm2
.useFlashBlock
) {
4523 flashBlockHandler();
4528 // call user-defined "onload", scoped to window
4530 if (typeof sm2
.onload
=== 'function') {
4532 sm2
.onload
.apply(window
);
4533 _wDS('onloadOK', 1);
4536 if (sm2
.waitForWindowLoad
) {
4537 event
.add(window
, 'load', initUserOnload
);
4544 detectFlash = function() {
4546 // 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
4548 if (hasFlash
!== _undefined
) {
4549 // this work has already been done.
4553 var hasPlugin
= false, n
= navigator
, nP
= n
.plugins
, obj
, type
, types
, AX
= window
.ActiveXObject
;
4555 if (nP
&& nP
.length
) {
4556 type
= 'application/x-shockwave-flash';
4557 types
= n
.mimeTypes
;
4558 if (types
&& types
[type
] && types
[type
].enabledPlugin
&& types
[type
].enabledPlugin
.description
) {
4561 } else if (AX
!== _undefined
&& !ua
.match(/MSAppHost/i)) {
4562 // 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.
4564 obj
= new AX('ShockwaveFlash.ShockwaveFlash');
4568 hasPlugin
= (!!obj
);
4569 // cleanup, because it is ActiveX after all
4573 hasFlash
= hasPlugin
;
4579 featureCheck = function() {
4584 formats
= sm2
.audioFormats
,
4585 // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
4586 isSpecial
= (is_iDevice
&& !!(ua
.match(/os (1|2|3_0|3_1)/i)));
4590 // has Audio(), but is broken; let it load links directly.
4591 sm2
.hasHTML5
= false;
4593 // ignore flash case, however
4594 sm2
.html5Only
= true;
4597 sm2
.oMC
.style
.display
= 'none';
4604 if (sm2
.useHTML5Audio
) {
4606 if (!sm2
.html5
|| !sm2
.html5
.canPlayType
) {
4607 sm2
._wD('SoundManager: No HTML5 Audio() support detected.');
4608 sm2
.hasHTML5
= false;
4613 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);
4621 if (sm2
.useHTML5Audio
&& sm2
.hasHTML5
) {
4623 for (item
in formats
) {
4624 if (formats
.hasOwnProperty(item
)) {
4625 if ((formats
[item
].required
&& !sm2
.html5
.canPlayType(formats
[item
].type
)) || (sm2
.preferFlash
&& (sm2
.flash
[item
] || sm2
.flash
[formats
[item
].type
]))) {
4626 // flash may be required, or preferred for this format
4635 if (sm2
.ignoreFlash
) {
4639 sm2
.html5Only
= (sm2
.hasHTML5
&& sm2
.useHTML5Audio
&& !needsFlash
);
4641 return (!sm2
.html5Only
);
4645 parseURL = function(url
) {
4648 * Internal: Finds and returns the first playable URL (or failing that, the first URL.)
4649 * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
4652 var i
, j
, urlResult
= 0, result
;
4654 if (url
instanceof Array
) {
4656 // find the first good one
4657 for (i
=0, j
=url
.length
; i
<j
; i
++) {
4659 if (url
[i
] instanceof Object
) {
4661 if (sm2
.canPlayMIME(url
[i
].type
)) {
4666 } else if (sm2
.canPlayURL(url
[i
])) {
4674 // normalize to string
4675 if (url
[urlResult
].url
) {
4676 url
[urlResult
] = url
[urlResult
].url
;
4679 result
= url
[urlResult
];
4693 startTimer = function(oSound
) {
4696 * attach a timer to this sound, and start an interval if needed
4699 if (!oSound
._hasTimer
) {
4701 oSound
._hasTimer
= true;
4703 if (!mobileHTML5
&& sm2
.html5PollingInterval
) {
4705 if (h5IntervalTimer
=== null && h5TimerCount
=== 0) {
4707 h5IntervalTimer
= setInterval(timerExecute
, sm2
.html5PollingInterval
);
4719 stopTimer = function(oSound
) {
4725 if (oSound
._hasTimer
) {
4727 oSound
._hasTimer
= false;
4729 if (!mobileHTML5
&& sm2
.html5PollingInterval
) {
4731 // interval will stop itself at next execution.
4741 timerExecute = function() {
4744 * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)
4749 if (h5IntervalTimer
!== null && !h5TimerCount
) {
4751 // no active timers, stop polling interval.
4753 clearInterval(h5IntervalTimer
);
4755 h5IntervalTimer
= null;
4761 // check all HTML5 sounds with timers
4763 for (i
= sm2
.soundIDs
.length
-1; i
>= 0; i
--) {
4765 if (sm2
.sounds
[sm2
.soundIDs
[i
]].isHTML5
&& sm2
.sounds
[sm2
.soundIDs
[i
]]._hasTimer
) {
4767 sm2
.sounds
[sm2
.soundIDs
[i
]]._onTimer();
4775 catchError = function(options
) {
4777 options
= (options
!== _undefined
? options
: {});
4779 if (typeof sm2
.onerror
=== 'function') {
4780 sm2
.onerror
.apply(window
, [{type
:(options
.type
!== _undefined
? options
.type
: null)}]);
4783 if (options
.fatal
!== _undefined
&& options
.fatal
) {
4789 badSafariFix = function() {
4791 // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4
4792 if (!isBadSafari
|| !detectFlash()) {
4797 var aF
= sm2
.audioFormats
, i
, item
;
4800 if (aF
.hasOwnProperty(item
)) {
4801 if (item
=== 'mp3' || item
=== 'mp4') {
4802 sm2
._wD(sm
+ ': Using flash fallback for ' + item
+ ' format');
4803 sm2
.html5
[item
] = false;
4804 // assign result to related formats, too
4805 if (aF
[item
] && aF
[item
].related
) {
4806 for (i
= aF
[item
].related
.length
-1; i
>= 0; i
--) {
4807 sm2
.html5
[aF
[item
].related
[i
]] = false;
4817 * Pseudo-private flash/ExternalInterface methods
4818 * ----------------------------------------------
4821 this._setSandboxType = function(sandboxType
) {
4824 var sb
= sm2
.sandbox
;
4826 sb
.type
= sandboxType
;
4827 sb
.description
= sb
.types
[(sb
.types
[sandboxType
] !== _undefined
?sandboxType
:'unknown')];
4829 if (sb
.type
=== 'localWithFile') {
4835 } else if (sb
.type
=== 'localWithNetwork') {
4837 sb
.noRemote
= false;
4840 } else if (sb
.type
=== 'localTrusted') {
4842 sb
.noRemote
= false;
4850 this._externalInterfaceOK = function(flashDate
, swfVersion
) {
4852 // flash callback confirming flash loaded, EI working etc.
4853 // flashDate = approx. timing/delay info for JS/flash bridge
4854 // swfVersion: SWF build string
4856 if (sm2
.swfLoaded
) {
4862 debugTS('swf', true);
4863 debugTS('flashtojs', true);
4864 sm2
.swfLoaded
= true;
4865 tryInitOnFocus
= false;
4871 // complain if JS + SWF build/version strings don't match, excluding +DEV builds
4873 if (!swfVersion
|| swfVersion
.replace(/\+dev/i,'') !== sm2
.versionNumber
.replace(/\+dev/i, '')) {
4875 e
= sm
+ ': Fatal: JavaScript file build "' + sm2
.versionNumber
+ '" does not match Flash SWF build "' + swfVersion
+ '" at ' + sm2
.url
+ '. Ensure both are up-to-date.';
4877 // escape flash -> JS stack so this error fires in window.
4878 setTimeout(function versionMismatch() {
4882 // exit, init will fail with timeout
4888 // slight delay before init
4889 setTimeout(init
, isIE
? 100 : 1);
4894 * Private initialization helpers
4895 * ------------------------------
4898 createMovie = function(smID
, smURL
) {
4900 if (didAppend
&& appendSuccess
) {
4901 // ignore if already succeeded
4905 function initMsg() {
4909 var options
= [], title
, str
= [], delimiter
= ' + ';
4911 title
= 'SoundManager ' + sm2
.version
+ (!sm2
.html5Only
&& sm2
.useHTML5Audio
? (sm2
.hasHTML5
? ' + HTML5 audio' : ', no HTML5 audio support') : '');
4913 if (!sm2
.html5Only
) {
4915 if (sm2
.preferFlash
) {
4916 options
.push('preferFlash');
4919 if (sm2
.useHighPerformance
) {
4920 options
.push('useHighPerformance');
4923 if (sm2
.flashPollingInterval
) {
4924 options
.push('flashPollingInterval (' + sm2
.flashPollingInterval
+ 'ms)');
4927 if (sm2
.html5PollingInterval
) {
4928 options
.push('html5PollingInterval (' + sm2
.html5PollingInterval
+ 'ms)');
4932 options
.push('wmode (' + sm2
.wmode
+ ')');
4935 if (sm2
.debugFlash
) {
4936 options
.push('debugFlash');
4939 if (sm2
.useFlashBlock
) {
4940 options
.push('flashBlock');
4945 if (sm2
.html5PollingInterval
) {
4946 options
.push('html5PollingInterval (' + sm2
.html5PollingInterval
+ 'ms)');
4951 if (options
.length
) {
4952 str
= str
.concat([options
.join(delimiter
)]);
4955 sm2
._wD(title
+ (str
.length
? delimiter
+ str
.join(', ') : ''), 1);
4963 if (sm2
.html5Only
) {
4969 sm2
.oMC
= id(sm2
.movieID
);
4972 // prevent multiple init attempts
4975 appendSuccess
= true;
4982 var remoteURL
= (smURL
|| sm2
.url
),
4983 localURL
= (sm2
.altURL
|| remoteURL
),
4984 swfTitle
= 'JS/Flash audio component (SoundManager 2)',
4985 oTarget
= getDocument(),
4986 extraClass
= getSWFCSS(),
4988 html
= doc
.getElementsByTagName('html')[0],
4989 oEmbed
, oMovie
, tmp
, movieHTML
, oEl
, s
, x
, sClass
;
4991 isRTL
= (html
&& html
.dir
&& html
.dir
.match(/rtl/i));
4992 smID
= (smID
=== _undefined
?sm2
.id
:smID
);
4994 function param(name
, value
) {
4995 return '<param name="'+name
+'" value="'+value
+'" />';
4998 // safety check for legacy (change to Flash 9 URL)
5000 sm2
.url
= normalizeMovieURL(overHTTP
?remoteURL
:localURL
);
5003 sm2
.wmode
= (!sm2
.wmode
&& sm2
.useHighPerformance
? 'transparent' : sm2
.wmode
);
5005 if (sm2
.wmode
!== null && (ua
.match(/msie 8/i) || (!isIE
&& !sm2
.useHighPerformance
)) && navigator
.platform
.match(/win32|win64/i)) {
5007 * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here
5008 * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout
5009 * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)
5011 messages
.push(strings
.spcWmode
);
5020 'allowScriptAccess': sm2
.allowScriptAccess
,
5021 'bgcolor': sm2
.bgColor
,
5022 'pluginspage': http
+'www.macromedia.com/go/getflashplayer',
5024 'type': 'application/x-shockwave-flash',
5026 // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
5027 'hasPriority': 'true'
5030 if (sm2
.debugFlash
) {
5031 oEmbed
.FlashVars
= 'debug=1';
5035 // don't write empty attribute
5036 delete oEmbed
.wmode
;
5042 oMovie
= doc
.createElement('div');
5044 '<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">',
5045 param('movie', smURL
),
5046 param('AllowScriptAccess', sm2
.allowScriptAccess
),
5047 param('quality', oEmbed
.quality
),
5048 (sm2
.wmode
? param('wmode', sm2
.wmode
): ''),
5049 param('bgcolor', sm2
.bgColor
),
5050 param('hasPriority', 'true'),
5051 (sm2
.debugFlash
? param('FlashVars', oEmbed
.FlashVars
) : ''),
5057 oMovie
= doc
.createElement('embed');
5058 for (tmp
in oEmbed
) {
5059 if (oEmbed
.hasOwnProperty(tmp
)) {
5060 oMovie
.setAttribute(tmp
, oEmbed
[tmp
]);
5067 extraClass
= getSWFCSS();
5068 oTarget
= getDocument();
5072 sm2
.oMC
= (id(sm2
.movieID
) || doc
.createElement('div'));
5076 sm2
.oMC
.id
= sm2
.movieID
;
5077 sm2
.oMC
.className
= swfCSS
.swfDefault
+ ' ' + extraClass
;
5081 if (!sm2
.useFlashBlock
) {
5082 if (sm2
.useHighPerformance
) {
5083 // on-screen at all times
5085 'position': 'fixed',
5088 // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.
5091 'overflow': 'hidden'
5094 // hide off-screen, lower priority
5096 'position': 'absolute',
5103 s
.left
= Math
.abs(parseInt(s
.left
,10))+'px';
5109 // soundcloud-reported render/crash fix, safari 5
5110 sm2
.oMC
.style
.zIndex
= 10000;
5113 if (!sm2
.debugFlash
) {
5115 if (s
.hasOwnProperty(x
)) {
5116 sm2
.oMC
.style
[x
] = s
[x
];
5123 sm2
.oMC
.appendChild(oMovie
);
5125 oTarget
.appendChild(sm2
.oMC
);
5127 oEl
= sm2
.oMC
.appendChild(doc
.createElement('div'));
5128 oEl
.className
= swfCSS
.swfBox
;
5129 oEl
.innerHTML
= movieHTML
;
5131 appendSuccess
= true;
5133 throw new Error(str('domError')+' \n'+e
.toString());
5138 // SM2 container is already in the document (eg. flashblock use case)
5139 sClass
= sm2
.oMC
.className
;
5140 sm2
.oMC
.className
= (sClass
?sClass
+' ':swfCSS
.swfDefault
) + (extraClass
?' '+extraClass
:'');
5141 sm2
.oMC
.appendChild(oMovie
);
5143 oEl
= sm2
.oMC
.appendChild(doc
.createElement('div'));
5144 oEl
.className
= swfCSS
.swfBox
;
5145 oEl
.innerHTML
= movieHTML
;
5147 appendSuccess
= true;
5155 // sm2._wD(sm + ': Trying to load ' + smURL + (!overHTTP && sm2.altURL ? ' (alternate URL)' : ''), 1);
5161 initMovie = function() {
5163 if (sm2
.html5Only
) {
5168 // attempt to get, or create, movie (may already exist)
5176 * Something isn't right - we've reached init, but the soundManager url property has not been set.
5177 * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.
5178 * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.
5186 // inline markup case
5187 flash
= sm2
.getMovie(sm2
.id
);
5192 createMovie(sm2
.id
, sm2
.url
);
5194 // try to re-append removed movie after reboot()
5196 sm2
.oMC
.appendChild(oRemoved
);
5198 sm2
.oMC
.innerHTML
= oRemovedHTML
;
5203 flash
= sm2
.getMovie(sm2
.id
);
5206 if (typeof sm2
.oninitmovie
=== 'function') {
5207 setTimeout(sm2
.oninitmovie
, 1);
5218 delayWaitForEI = function() {
5220 setTimeout(waitForEI
, 1000);
5224 waitForEI = function() {
5227 loadIncomplete
= false;
5230 // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.
5238 waitingForEI
= true;
5239 event
.remove(window
, 'load', delayWaitForEI
);
5241 if (tryInitOnFocus
&& !isFocused
) {
5242 // Safari won't load flash in background tabs, only when focused.
5248 p
= sm2
.getMoviePercent();
5249 if (p
> 0 && p
< 100) {
5250 loadIncomplete
= true;
5254 setTimeout(function() {
5256 p
= sm2
.getMoviePercent();
5258 if (loadIncomplete
) {
5259 // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
5260 waitingForEI
= false;
5261 sm2
._wD(str('waitSWF'));
5262 window
.setTimeout(delayWaitForEI
, 1);
5268 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);
5269 if (!overHTTP
&& p
) {
5270 _wDS('localFail', 2);
5271 if (!sm2
.debugFlash
) {
5272 _wDS('tryDebug', 2);
5276 // if 0 (not null), probably a 404.
5277 sm2
._wD(str('swf404', sm2
.url
), 1);
5279 debugTS('flashtojs', false, ': Timed out' + overHTTP
?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');
5283 // give up / time-out, depending
5285 if (!didInit
&& okToDisable
) {
5287 // SWF failed. Maybe blocked.
5288 if (sm2
.useFlashBlock
|| sm2
.flashLoadTimeout
=== 0) {
5289 if (sm2
.useFlashBlock
) {
5290 flashBlockHandler();
5292 _wDS('waitForever');
5294 // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.
5295 _wDS('waitForever');
5296 // fire any regular registered ontimeout() listeners.
5297 processOnEvents({type
:'ontimeout', ignoreInit
: true});
5300 // flash loaded? Shouldn't be a blocking issue, then.
5301 if (sm2
.flashLoadTimeout
=== 0) {
5302 _wDS('waitForever');
5309 }, sm2
.flashLoadTimeout
);
5313 handleFocus = function() {
5315 function cleanup() {
5316 event
.remove(window
, 'focus', handleFocus
);
5319 if (isFocused
|| !tryInitOnFocus
) {
5320 // already focused, or not special Safari background tab case
5329 // allow init to restart
5330 waitingForEI
= false;
5332 // kick off ExternalInterface timeout, now that the SWF has started
5340 flushMessages = function() {
5344 // SM2 pre-init debug messages
5345 if (messages
.length
) {
5346 sm2
._wD('SoundManager 2: ' + messages
.join(' '), 1);
5354 showSupport = function() {
5360 var item
, tests
= [];
5362 if (sm2
.useHTML5Audio
&& sm2
.hasHTML5
) {
5363 for (item
in sm2
.audioFormats
) {
5364 if (sm2
.audioFormats
.hasOwnProperty(item
)) {
5365 tests
.push(item
+ ' = ' + sm2
.html5
[item
] + (!sm2
.html5
[item
] && hasFlash
&& sm2
.flash
[item
] ? ' (using flash)' : (sm2
.preferFlash
&& sm2
.flash
[item
] && hasFlash
? ' (preferring flash)': (!sm2
.html5
[item
] ? ' (' + (sm2
.audioFormats
[item
].required
? 'required, ':'') + 'and no flash support)' : ''))));
5368 sm2
._wD('SoundManager 2 HTML5 support: ' + tests
.join(', '), 1);
5375 initComplete = function(bNoDisable
) {
5381 if (sm2
.html5Only
) {
5386 debugTS('onload', true);
5390 var wasTimeout
= (sm2
.useFlashBlock
&& sm2
.flashLoadTimeout
&& !sm2
.getMoviePercent()),
5397 error
= {type
: (!hasFlash
&& needsFlash
? 'NO_FLASH' : 'INIT_TIMEOUT')};
5401 sm2
._wD('SoundManager 2 ' + (disabled
? 'failed to load' : 'loaded') + ' (' + (disabled
? 'Flash security/load error' : 'OK') + ')', disabled
? 2: 1);
5403 if (disabled
|| bNoDisable
) {
5404 if (sm2
.useFlashBlock
&& sm2
.oMC
) {
5405 sm2
.oMC
.className
= getSWFCSS() + ' ' + (sm2
.getMoviePercent() === null?swfCSS
.swfTimedout
:swfCSS
.swfError
);
5407 processOnEvents({type
:'ontimeout', error
:error
, ignoreInit
: true});
5408 debugTS('onload', false);
5412 debugTS('onload', true);
5416 if (sm2
.waitForWindowLoad
&& !windowLoaded
) {
5418 event
.add(window
, 'load', initUserOnload
);
5421 if (sm2
.waitForWindowLoad
&& windowLoaded
) {
5434 * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
5435 * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
5438 setProperties = function() {
5441 o
= sm2
.setupOptions
;
5445 if (o
.hasOwnProperty(i
)) {
5447 // assign local property if not already defined
5449 if (sm2
[i
] === _undefined
) {
5453 } else if (sm2
[i
] !== o
[i
]) {
5455 // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
5456 sm2
.setupOptions
[i
] = sm2
[i
];
5469 // called after onload()
5476 function cleanup() {
5477 event
.remove(window
, 'load', sm2
.beginDelayedInit
);
5480 if (sm2
.html5Only
) {
5482 // we don't need no steenking flash!
5495 // attempt to talk to Flash
5496 flash
._externalInterfaceTest(false);
5498 // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling
5499 // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)
5500 setPolling(true, (sm2
.flashPollingInterval
|| (sm2
.useHighPerformance
? 10 : 50)));
5502 if (!sm2
.debugMode
) {
5503 // stop the SWF from making debug output calls to JS
5504 flash
._disableDebug();
5508 debugTS('jstoflash', true);
5510 if (!sm2
.html5Only
) {
5511 // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead
5512 // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
5513 event
.add(window
, 'unload', doNothing
);
5518 sm2
._wD('js/flash exception: ' + e
.toString());
5519 debugTS('jstoflash', false);
5520 catchError({type
:'JS_TO_FLASH_EXCEPTION', fatal
:true});
5521 // don't disable, for reboot()
5531 // disconnect events
5538 domContentLoaded = function() {
5546 // assign top-level soundManager properties eg. soundManager.url
5552 * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1
5553 * Ditto for sm2-preferFlash, too.
5558 var a
= 'sm2-usehtml5audio=',
5559 a2
= 'sm2-preferflash=',
5562 hasCon
= (window
.console
!== _undefined
&& typeof console
.log
=== 'function'),
5563 l
= wl
.toLowerCase();
5565 if (l
.indexOf(a
) !== -1) {
5566 b
= (l
.charAt(l
.indexOf(a
)+a
.length
) === '1');
5568 console
.log((b
?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
5575 if (l
.indexOf(a2
) !== -1) {
5576 b2
= (l
.charAt(l
.indexOf(a2
)+a2
.length
) === '1');
5578 console
.log((b2
?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
5588 if (!hasFlash
&& sm2
.hasHTML5
) {
5589 sm2
._wD('SoundManager: No Flash detected' + (!sm2
.useHTML5Audio
? ', enabling HTML5.' : '. Trying HTML5-only mode.'), 1);
5591 'useHTML5Audio': true,
5592 // make sure we aren't preferring flash, either
5593 // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
5594 'preferFlash': false
5599 sm2
.html5
.usingFlash
= featureCheck();
5600 needsFlash
= sm2
.html5
.usingFlash
;
5602 if (!hasFlash
&& needsFlash
) {
5603 messages
.push(strings
.needFlash
);
5604 // TODO: Fatal here vs. timeout approach, etc.
5605 // hack: fail sooner.
5607 'flashLoadTimeout': 1
5611 if (doc
.removeEventListener
) {
5612 doc
.removeEventListener('DOMContentLoaded', domContentLoaded
, false);
5621 domContentLoadedIE = function() {
5623 if (doc
.readyState
=== 'complete') {
5625 doc
.detachEvent('onreadystatechange', domContentLoadedIE
);
5632 winOnLoad = function() {
5634 // catch edge case of initComplete() firing after window.load()
5635 windowLoaded
= true;
5636 event
.remove(window
, 'load', winOnLoad
);
5641 * miscellaneous run-time, pre-init stuff
5644 preInit = function() {
5648 // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
5651 if (!sm2
.setupOptions
.useHTML5Audio
|| sm2
.setupOptions
.preferFlash
) {
5652 // notify that defaults are being changed.
5653 messages
.push(strings
.mobileUA
);
5657 sm2
.setupOptions
.useHTML5Audio
= true;
5658 sm2
.setupOptions
.preferFlash
= false;
5660 if (is_iDevice
|| (isAndroid
&& !ua
.match(/android\s2\.3/i))) {
5661 // iOS and Android devices tend to work better with a single audio instance, specifically for chained playback of sounds in sequence.
5662 // common use case: exiting sound onfinish() -> createSound() -> play()
5664 messages
.push(strings
.globalHTML5
);
5667 sm2
.ignoreFlash
= true;
5669 useGlobalHTML5Audio
= true;
5681 // focus and window load, init (primarily flash-driven)
5682 event
.add(window
, 'focus', handleFocus
);
5683 event
.add(window
, 'load', delayWaitForEI
);
5684 event
.add(window
, 'load', winOnLoad
);
5686 if (doc
.addEventListener
) {
5688 doc
.addEventListener('DOMContentLoaded', domContentLoaded
, false);
5690 } else if (doc
.attachEvent
) {
5692 doc
.attachEvent('onreadystatechange', domContentLoadedIE
);
5696 // no add/attachevent support - safe to assume no JS -> Flash either
5697 debugTS('onload', false);
5698 catchError({type
:'NO_DOM2_EVENTS', fatal
:true});
5704 // SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading
5706 if (window
.SM2_DEFER
=== undefined || !SM2_DEFER
) {
5707 soundManager
= new SoundManager();
5711 * SoundManager public interfaces
5712 * ------------------------------
5715 window
.SoundManager
= SoundManager
; // constructor
5716 window
.soundManager
= soundManager
; // public API, flash callbacks etc.