]> jfr.im git - irc/quakenet/qwebirc.git/blame - static/js/soundmanager2.js
Split up about.html lines.
[irc/quakenet/qwebirc.git] / static / js / soundmanager2.js
CommitLineData
45b0fa89
CP
1/** @license\r
2 *\r
3 * SoundManager 2: JavaScript Sound for the Web\r
4 * ----------------------------------------------\r
5 * http://schillmania.com/projects/soundmanager2/\r
6 *\r
7 * Copyright (c) 2007, Scott Schiller. All rights reserved.\r
8 * Code provided under the BSD License:\r
9 * http://schillmania.com/projects/soundmanager2/license.txt\r
10 *\r
11 * V2.97a.20131201\r
12 */\r
13\r
14/*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio, opera */\r
15/*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true, todo: true */\r
16\r
17/**\r
18 * About this file\r
19 * -------------------------------------------------------------------------------------\r
20 * This is the fully-commented source version of the SoundManager 2 API,\r
21 * recommended for use during development and testing.\r
22 *\r
23 * See soundmanager2-nodebug-jsmin.js for an optimized build (~11KB with gzip.)\r
24 * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion\r
25 * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.)\r
26 *\r
27 * You may notice <d> and </d> comments in this source; these are delimiters for\r
28 * debug blocks which are removed in the -nodebug builds, further optimizing code size.\r
29 *\r
30 * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;)\r
31 */\r
32\r
33(function(window, _undefined) {\r
34\r
35"use strict";\r
127631e0 36\r
45b0fa89 37var soundManager = null;\r
127631e0 38\r
45b0fa89
CP
39/**\r
40 * The SoundManager constructor.\r
41 *\r
42 * @constructor\r
43 * @param {string} smURL Optional: Path to SWF files\r
44 * @param {string} smID Optional: The ID to use for the SWF container element\r
45 * @this {SoundManager}\r
46 * @return {SoundManager} The new SoundManager instance\r
47 */\r
48\r
49function SoundManager(smURL, smID) {\r
50\r
51 /**\r
52 * soundManager configuration options list\r
53 * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)\r
54 * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})\r
55 */\r
127631e0 56\r
45b0fa89
CP
57 this.setupOptions = {\r
58\r
59 'url': (smURL || null), // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/\r
60 'flashVersion': 8, // flash build to use (8 or 9.) Some API features require 9.\r
61 'debugMode': true, // enable debugging output (console.log() with HTML fallback)\r
62 'debugFlash': false, // enable debugging output inside SWF, troubleshoot Flash/browser issues\r
63 'useConsole': true, // use console.log() if available (otherwise, writes to #soundmanager-debug element)\r
64 'consoleOnly': true, // if console is being used, do not create/write to #soundmanager-debug\r
65 'waitForWindowLoad': false, // force SM2 to wait for window.onload() before trying to call soundManager.onload()\r
66 'bgColor': '#ffffff', // SWF background color. N/A when wmode = 'transparent'\r
67 'useHighPerformance': false, // position:fixed flash movie can help increase js/flash speed, minimize lag\r
68 'flashPollingInterval': null, // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.\r
69 'html5PollingInterval': null, // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.\r
70 'flashLoadTimeout': 1000, // msec to wait for flash movie to load before failing (0 = infinity)\r
71 'wmode': null, // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)\r
72 'allowScriptAccess': 'always', // for scripting the SWF (object/embed property), 'always' or 'sameDomain'\r
73 'useFlashBlock': false, // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.\r
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.\r
75 'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.\r
76 'preferFlash': false, // overrides useHTML5audio, will use Flash for MP3/MP4/AAC if present. Potential option if HTML5 playback with these formats is quirky.\r
77 'noSWFCache': false, // if true, appends ?ts={date} to break aggressive SWF caching.\r
78 'idPrefix': 'sound' // if an id is not provided to createSound(), this prefix is used for generated IDs - 'sound0', 'sound1' etc.\r
5ad04c7b 79\r
45b0fa89 80 };\r
127631e0
CP
81\r
82 this.defaultOptions = {\r
45b0fa89
CP
83\r
84 /**\r
85 * the default configuration for sound objects made with createSound() and related methods\r
86 * eg., volume, auto-load behaviour and so forth\r
87 */\r
88\r
89 'autoLoad': false, // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)\r
90 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true)\r
91 'from': null, // position to start playback within a sound (msec), default = beginning\r
92 'loops': 1, // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)\r
93 'onid3': null, // callback function for "ID3 data is added/available"\r
94 'onload': null, // callback function for "load finished"\r
95 'whileloading': null, // callback function for "download progress update" (X of Y bytes received)\r
96 'onplay': null, // callback for "play" start\r
97 'onpause': null, // callback for "pause"\r
98 'onresume': null, // callback for "resume" (pause toggle)\r
99 'whileplaying': null, // callback during play (position update)\r
100 'onposition': null, // object containing times and function callbacks for positions of interest\r
101 'onstop': null, // callback for "user stop"\r
102 'onfailure': null, // callback function for when playing fails\r
103 'onfinish': null, // callback function for "sound finished playing"\r
104 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time\r
105 'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled\r
106 'position': null, // offset (milliseconds) to seek to within loaded sound data.\r
107 'pan': 0, // "pan" settings, left-to-right, -100 to 100\r
108 'stream': true, // allows playing before entire file has loaded (recommended)\r
109 'to': null, // position to end playback within a sound (msec), default = end\r
110 'type': null, // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3\r
111 'usePolicyFile': false, // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)\r
112 'volume': 100 // self-explanatory. 0-100, the latter being the max.\r
113\r
114 };\r
115\r
116 this.flash9Options = {\r
117\r
118 /**\r
119 * flash 9-only options,\r
120 * merged into defaultOptions if flash 9 is being used\r
121 */\r
122\r
123 'isMovieStar': null, // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL\r
124 'usePeakData': false, // enable left/right channel peak (level) data\r
125 'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.\r
126 'useEQData': false, // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.\r
127 'onbufferchange': null, // callback for "isBuffering" property change\r
128 'ondataerror': null // callback for waveform/eq data access error (flash playing audio in other tabs/domains)\r
129\r
130 };\r
131\r
132 this.movieStarOptions = {\r
133\r
134 /**\r
135 * flash 9.0r115+ MPEG4 audio options,\r
136 * merged into defaultOptions if flash 9+movieStar mode is enabled\r
137 */\r
138\r
139 'bufferTime': 3, // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)\r
140 'serverURL': null, // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants\r
141 'onconnect': null, // rtmp: callback for connection to flash media server\r
142 'duration': null // rtmp: song duration (msec)\r
143\r
144 };\r
145\r
146 this.audioFormats = {\r
147\r
148 /**\r
149 * determines HTML5 support + flash requirements.\r
150 * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.\r
151 * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)\r
152 */\r
153\r
154 'mp3': {\r
155 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],\r
156 'required': true\r
157 },\r
158\r
159 'mp4': {\r
160 'related': ['aac','m4a','m4b'], // additional formats under the MP4 container\r
161 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],\r
162 'required': false\r
163 },\r
164\r
165 'ogg': {\r
166 'type': ['audio/ogg; codecs=vorbis'],\r
167 'required': false\r
168 },\r
169\r
170 'opus': {\r
171 'type': ['audio/ogg; codecs=opus', 'audio/opus'],\r
172 'required': false\r
173 },\r
174\r
175 'wav': {\r
176 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],\r
177 'required': false\r
178 }\r
179\r
180 };\r
181\r
182 // HTML attributes (id + class names) for the SWF container\r
183\r
184 this.movieID = 'sm2-container';\r
185 this.id = (smID || 'sm2movie');\r
186\r
187 this.debugID = 'soundmanager-debug';\r
188 this.debugURLParam = /([#?&])debug=1/i;\r
189\r
190 // dynamic attributes\r
191\r
192 this.versionNumber = 'V2.97a.20131201';\r
127631e0 193 this.version = null;\r
127631e0 194 this.movieURL = null;\r
127631e0
CP
195 this.altURL = null;\r
196 this.swfLoaded = false;\r
197 this.enabled = false;\r
127631e0 198 this.oMC = null;\r
5ad04c7b 199 this.sounds = {};\r
127631e0
CP
200 this.soundIDs = [];\r
201 this.muted = false;\r
45b0fa89
CP
202 this.didFlashBlock = false;\r
203 this.filePattern = null;\r
5ad04c7b 204\r
127631e0 205 this.filePatterns = {\r
45b0fa89
CP
206\r
207 'flash8': /\.mp3(\?.*)?$/i,\r
208 'flash9': /\.mp3(\?.*)?$/i\r
209\r
127631e0 210 };\r
5ad04c7b 211\r
45b0fa89 212 // support indicators, set at init\r
5ad04c7b 213\r
127631e0 214 this.features = {\r
45b0fa89
CP
215\r
216 'buffering': false,\r
217 'peakData': false,\r
218 'waveformData': false,\r
219 'eqData': false,\r
220 'movieStar': false\r
221\r
127631e0
CP
222 };\r
223\r
45b0fa89
CP
224 // flash sandbox info, used primarily in troubleshooting\r
225\r
127631e0 226 this.sandbox = {\r
45b0fa89
CP
227\r
228 // <d>\r
127631e0
CP
229 'type': null,\r
230 'types': {\r
231 'remote': 'remote (domain-based) rules',\r
232 'localWithFile': 'local with file access (no internet access)',\r
233 'localWithNetwork': 'local with network (internet access only, no local access)',\r
45b0fa89 234 'localTrusted': 'local, trusted (local+internet access)'\r
127631e0
CP
235 },\r
236 'description': null,\r
237 'noRemote': null,\r
238 'noLocal': null\r
45b0fa89 239 // </d>\r
127631e0 240\r
5ad04c7b 241 };\r
127631e0 242\r
45b0fa89
CP
243 /**\r
244 * format support (html5/flash)\r
245 * stores canPlayType() results based on audioFormats.\r
246 * eg. { mp3: boolean, mp4: boolean }\r
247 * treat as read-only.\r
248 */\r
127631e0 249\r
45b0fa89
CP
250 this.html5 = {\r
251 'usingFlash': null // set if/when flash fallback is needed\r
86f67adc
CP
252 };\r
253\r
45b0fa89
CP
254 // file type support hash\r
255 this.flash = {};\r
127631e0 256\r
45b0fa89
CP
257 // determined at init time\r
258 this.html5Only = false;\r
127631e0 259\r
45b0fa89
CP
260 // used for special cases (eg. iPad/iPhone/palm OS?)\r
261 this.ignoreFlash = false;\r
127631e0 262\r
45b0fa89
CP
263 /**\r
264 * a few private internals (OK, a lot. :D)\r
265 */\r
127631e0 266\r
45b0fa89
CP
267 var SMSound,\r
268 sm2 = this, globalHTML5Audio = null, flash = null, sm = 'soundManager', smc = sm + ': ', h5 = 'HTML5::', id, ua = navigator.userAgent, wl = window.location.href.toString(), doc = document, doNothing, setProperties, init, fV, on_queue = [], debugOpen = true, debugTS, didAppend = false, appendSuccess = false, didInit = false, disabled = false, windowLoaded = false, _wDS, wdCount = 0, initComplete, mixin, assign, extraOptions, addOnEvent, processOnEvents, initUserOnload, delayWaitForEI, waitForEI, rebootIntoHTML5, setVersionInfo, handleFocus, strings, initMovie, preInit, domContentLoaded, winOnLoad, didDCLoaded, getDocument, createMovie, catchError, setPolling, initDebug, debugLevels = ['log', 'info', 'warn', 'error'], defaultFlashVersion = 8, disableObject, failSafely, normalizeMovieURL, oRemoved = null, oRemovedHTML = null, str, flashBlockHandler, getSWFCSS, swfCSS, toggleDebug, loopFix, policyFix, complain, idCheck, waitingForEI = false, initPending = false, startTimer, stopTimer, timerExecute, h5TimerCount = 0, h5IntervalTimer = null, parseURL, messages = [],\r
269 canIgnoreFlash, needsFlash = null, featureCheck, html5OK, html5CanPlay, html5Ext, html5Unload, domContentLoadedIE, testHTML5, event, slice = Array.prototype.slice, useGlobalHTML5Audio = false, lastGlobalHTML5URL, hasFlash, detectFlash, badSafariFix, html5_events, showSupport, flushMessages, wrapCallback, idCounter = 0,\r
270 is_iDevice = ua.match(/(ipad|iphone|ipod)/i), isAndroid = ua.match(/android/i), isIE = ua.match(/msie/i), isWebkit = ua.match(/webkit/i), isSafari = (ua.match(/safari/i) && !ua.match(/chrome/i)), isOpera = (ua.match(/opera/i)),\r
271 mobileHTML5 = (ua.match(/(mobile|pre\/|xoom)/i) || is_iDevice || isAndroid),\r
272 isBadSafari = (!wl.match(/usehtml5audio/i) && !wl.match(/sm2\-ignorebadua/i) && isSafari && !ua.match(/silk/i) && ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159\r
273 hasConsole = (window.console !== _undefined && console.log !== _undefined), isFocused = (doc.hasFocus !== _undefined?doc.hasFocus():null), tryInitOnFocus = (isSafari && (doc.hasFocus === _undefined || !doc.hasFocus())), okToDisable = !tryInitOnFocus, flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i, msecScale = 1000,\r
274 emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)\r
275 emptyWAV = 'data:audio/wave;base64,/UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAD//w==', // tiny WAV for HTML5 unloading\r
276 overHTTP = (doc.location?doc.location.protocol.match(/http/i):null),\r
277 http = (!overHTTP ? 'http:/'+'/' : ''),\r
278 // mp3, mp4, aac etc.\r
279 netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,\r
280 // Flash v9.0r115+ "moviestar" formats\r
281 netStreamTypes = ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],\r
282 netStreamPattern = new RegExp('\\.(' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');\r
283\r
284 this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set\r
285\r
286 // use altURL if not "online"\r
287 this.useAltURL = !overHTTP;\r
288\r
289 swfCSS = {\r
290\r
291 'swfBox': 'sm2-object-box',\r
292 'swfDefault': 'movieContainer',\r
293 'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)\r
294 'swfTimedout': 'swf_timedout',\r
295 'swfLoaded': 'swf_loaded',\r
296 'swfUnblocked': 'swf_unblocked', // or loaded OK\r
297 'sm2Debug': 'sm2_debug',\r
298 'highPerf': 'high_performance',\r
299 'flashDebug': 'flash_debug'\r
127631e0 300\r
127631e0
CP
301 };\r
302\r
45b0fa89
CP
303 /**\r
304 * basic HTML5 Audio() support test\r
305 * try...catch because of IE 9 "not implemented" nonsense\r
306 * https://github.com/Modernizr/Modernizr/issues/224\r
307 */\r
127631e0 308\r
45b0fa89
CP
309 this.hasHTML5 = (function() {\r
310 try {\r
311 // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.\r
312 return (Audio !== _undefined && (isOpera && opera !== _undefined && opera.version() < 10 ? new Audio(null) : new Audio()).canPlayType !== _undefined);\r
313 } catch(e) {\r
5ad04c7b
CP
314 return false;\r
315 }\r
45b0fa89 316 }());\r
127631e0 317\r
45b0fa89
CP
318 /**\r
319 * Public SoundManager API\r
320 * -----------------------\r
321 */\r
127631e0 322\r
45b0fa89
CP
323 /**\r
324 * Configures top-level soundManager properties.\r
325 *\r
326 * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }\r
327 * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.\r
328 */\r
127631e0 329\r
45b0fa89 330 this.setup = function(options) {\r
127631e0 331\r
45b0fa89 332 var noURL = (!sm2.url);\r
127631e0 333\r
45b0fa89 334 // warn if flash options have already been applied\r
127631e0 335\r
45b0fa89
CP
336 if (options !== _undefined && didInit && needsFlash && sm2.ok() && (options.flashVersion !== _undefined || options.url !== _undefined || options.html5Test !== _undefined)) {\r
337 complain(str('setupLate'));\r
5ad04c7b 338 }\r
127631e0 339\r
45b0fa89 340 // TODO: defer: true?\r
127631e0 341\r
45b0fa89 342 assign(options);\r
127631e0 343\r
45b0fa89 344 // special case 1: "Late setup". SM2 loaded normally, but user didn't assign flash URL eg., setup({url:...}) before SM2 init. Treat as delayed init.\r
127631e0 345\r
45b0fa89 346 if (options) {\r
127631e0 347\r
45b0fa89
CP
348 if (noURL && didDCLoaded && options.url !== _undefined) {\r
349 sm2.beginDelayedInit();\r
350 }\r
127631e0 351\r
45b0fa89 352 // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.\r
127631e0 353\r
45b0fa89
CP
354 if (!didDCLoaded && options.url !== _undefined && doc.readyState === 'complete') {\r
355 setTimeout(domContentLoaded, 1);\r
127631e0 356 }\r
45b0fa89 357\r
127631e0 358 }\r
127631e0 359\r
45b0fa89 360 return sm2;\r
127631e0 361\r
127631e0
CP
362 };\r
363\r
45b0fa89 364 this.ok = function() {\r
127631e0 365\r
45b0fa89 366 return (needsFlash ? (didInit && !disabled) : (sm2.useHTML5Audio && sm2.hasHTML5));\r
5ad04c7b 367\r
127631e0
CP
368 };\r
369\r
45b0fa89 370 this.supported = this.ok; // legacy\r
127631e0 371\r
45b0fa89 372 this.getMovie = function(smID) {\r
127631e0 373\r
45b0fa89
CP
374 // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version\r
375 return id(smID) || doc[smID] || window[smID];\r
127631e0 376\r
127631e0
CP
377 };\r
378\r
45b0fa89
CP
379 /**\r
380 * Creates a SMSound sound object instance.\r
381 *\r
382 * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)\r
383 * @return {object} SMSound The new SMSound object.\r
384 */\r
385\r
386 this.createSound = function(oOptions, _url) {\r
127631e0 387\r
45b0fa89 388 var cs, cs_string, options, oSound = null;\r
127631e0 389\r
45b0fa89
CP
390 // <d>\r
391 cs = sm + '.createSound(): ';\r
392 cs_string = cs + str(!didInit?'notReady':'notOK');\r
393 // </d>\r
127631e0 394\r
45b0fa89
CP
395 if (!didInit || !sm2.ok()) {\r
396 complain(cs_string);\r
397 return false;\r
86f67adc 398 }\r
86f67adc 399\r
45b0fa89
CP
400 if (_url !== _undefined) {\r
401 // function overloading in JS! :) ..assume simple createSound(id, url) use case\r
402 oOptions = {\r
403 'id': oOptions,\r
404 'url': _url\r
405 };\r
406 }\r
86f67adc 407\r
45b0fa89
CP
408 // inherit from defaultOptions\r
409 options = mixin(oOptions);\r
5ad04c7b 410\r
45b0fa89 411 options.url = parseURL(options.url);\r
127631e0 412\r
45b0fa89
CP
413 // generate an id, if needed.\r
414 if (options.id === undefined) {\r
415 options.id = sm2.setupOptions.idPrefix + (idCounter++);\r
86f67adc 416 }\r
45b0fa89
CP
417\r
418 // <d>\r
419 if (options.id.toString().charAt(0).match(/^[0-9]$/)) {\r
420 sm2._wD(cs + str('badID', options.id), 2);\r
127631e0 421 }\r
127631e0 422\r
45b0fa89
CP
423 sm2._wD(cs + options.id + (options.url ? ' (' + options.url + ')' : ''), 1);\r
424 // </d>\r
127631e0 425\r
45b0fa89
CP
426 if (idCheck(options.id, true)) {\r
427 sm2._wD(cs + options.id + ' exists', 1);\r
428 return sm2.sounds[options.id];\r
429 }\r
127631e0 430\r
45b0fa89
CP
431 function make() {\r
432\r
433 options = loopFix(options);\r
434 sm2.sounds[options.id] = new SMSound(options);\r
435 sm2.soundIDs.push(options.id);\r
436 return sm2.sounds[options.id];\r
5ad04c7b 437\r
86f67adc
CP
438 }\r
439\r
45b0fa89 440 if (html5OK(options)) {\r
5ad04c7b 441\r
45b0fa89
CP
442 oSound = make();\r
443 sm2._wD(options.id + ': Using HTML5');\r
444 oSound._setup_html5(options);\r
5ad04c7b 445\r
5ad04c7b 446 } else {\r
5ad04c7b 447\r
45b0fa89
CP
448 if (sm2.html5Only) {\r
449 sm2._wD(options.id + ': No HTML5 support for this sound, and no Flash. Exiting.');\r
450 return make();\r
451 }\r
5ad04c7b 452\r
45b0fa89 453 // TODO: Move HTML5/flash checks into generic URL parsing/handling function.\r
127631e0 454\r
45b0fa89
CP
455 if (sm2.html5.usingFlash && options.url && options.url.match(/data\:/i)) {\r
456 // data: URIs not supported by Flash, either.\r
457 sm2._wD(options.id + ': data: URIs not supported via Flash. Exiting.');\r
458 return make();\r
459 }\r
5ad04c7b 460\r
45b0fa89
CP
461 if (fV > 8) {\r
462 if (options.isMovieStar === null) {\r
463 // attempt to detect MPEG-4 formats\r
464 options.isMovieStar = !!(options.serverURL || (options.type ? options.type.match(netStreamMimeTypes) : false) || (options.url && options.url.match(netStreamPattern)));\r
127631e0 465 }\r
45b0fa89
CP
466 // <d>\r
467 if (options.isMovieStar) {\r
468 sm2._wD(cs + 'using MovieStar handling');\r
469 if (options.loops > 1) {\r
470 _wDS('noNSLoop');\r
5ad04c7b 471 }\r
127631e0 472 }\r
45b0fa89
CP
473 // </d>\r
474 }\r
475\r
476 options = policyFix(options, cs);\r
477 oSound = make();\r
478\r
479 if (fV === 8) {\r
480 flash._createSound(options.id, options.loops||1, options.usePolicyFile);\r
127631e0 481 } else {\r
45b0fa89
CP
482 flash._createSound(options.id, options.url, options.usePeakData, options.useWaveformData, options.useEQData, options.isMovieStar, (options.isMovieStar?options.bufferTime:false), options.loops||1, options.serverURL, options.duration||null, options.autoPlay, true, options.autoLoad, options.usePolicyFile);\r
483 if (!options.serverURL) {\r
484 // We are connected immediately\r
485 oSound.connected = true;\r
486 if (options.onconnect) {\r
487 options.onconnect.apply(oSound);\r
5ad04c7b
CP
488 }\r
489 }\r
5ad04c7b 490 }\r
45b0fa89
CP
491\r
492 if (!options.serverURL && (options.autoLoad || options.autoPlay)) {\r
493 // call load for non-rtmp streams\r
494 oSound.load(options);\r
495 }\r
496\r
5ad04c7b
CP
497 }\r
498\r
45b0fa89
CP
499 // rtmp will play in onconnect\r
500 if (!options.serverURL && options.autoPlay) {\r
501 oSound.play();\r
5ad04c7b
CP
502 }\r
503\r
45b0fa89
CP
504 return oSound;\r
505\r
127631e0
CP
506 };\r
507\r
45b0fa89
CP
508 /**\r
509 * Destroys a SMSound sound object instance.\r
510 *\r
511 * @param {string} sID The ID of the sound to destroy\r
512 */\r
513\r
514 this.destroySound = function(sID, _bFromSound) {\r
515\r
516 // explicitly destroy a sound before normal page unload, etc.\r
517\r
518 if (!idCheck(sID)) {\r
519 return false;\r
5ad04c7b 520 }\r
45b0fa89
CP
521\r
522 var oS = sm2.sounds[sID], i;\r
523\r
524 // Disable all callbacks while the sound is being destroyed\r
525 oS._iO = {};\r
526\r
527 oS.stop();\r
528 oS.unload();\r
529\r
530 for (i = 0; i < sm2.soundIDs.length; i++) {\r
531 if (sm2.soundIDs[i] === sID) {\r
532 sm2.soundIDs.splice(i, 1);\r
533 break;\r
86f67adc 534 }\r
5ad04c7b 535 }\r
45b0fa89
CP
536\r
537 if (!_bFromSound) {\r
538 // ignore if being called from SMSound instance\r
539 oS.destruct(true);\r
5ad04c7b 540 }\r
127631e0 541\r
45b0fa89
CP
542 oS = null;\r
543 delete sm2.sounds[sID];\r
127631e0 544\r
45b0fa89 545 return true;\r
127631e0 546\r
127631e0
CP
547 };\r
548\r
45b0fa89
CP
549 /**\r
550 * Calls the load() method of a SMSound object by ID.\r
551 *\r
552 * @param {string} sID The ID of the sound\r
553 * @param {object} oOptions Optional: Sound options\r
554 */\r
127631e0 555\r
45b0fa89 556 this.load = function(sID, oOptions) {\r
5ad04c7b 557\r
45b0fa89
CP
558 if (!idCheck(sID)) {\r
559 return false;\r
5ad04c7b 560 }\r
45b0fa89
CP
561 return sm2.sounds[sID].load(oOptions);\r
562\r
127631e0
CP
563 };\r
564\r
45b0fa89
CP
565 /**\r
566 * Calls the unload() method of a SMSound object by ID.\r
567 *\r
568 * @param {string} sID The ID of the sound\r
569 */\r
5ad04c7b 570\r
45b0fa89 571 this.unload = function(sID) {\r
127631e0 572\r
45b0fa89
CP
573 if (!idCheck(sID)) {\r
574 return false;\r
5ad04c7b 575 }\r
45b0fa89
CP
576 return sm2.sounds[sID].unload();\r
577\r
127631e0
CP
578 };\r
579\r
45b0fa89
CP
580 /**\r
581 * Calls the onPosition() method of a SMSound object by ID.\r
582 *\r
583 * @param {string} sID The ID of the sound\r
584 * @param {number} nPosition The position to watch for\r
585 * @param {function} oMethod The relevant callback to fire\r
586 * @param {object} oScope Optional: The scope to apply the callback to\r
587 * @return {SMSound} The SMSound object\r
588 */\r
589\r
590 this.onPosition = function(sID, nPosition, oMethod, oScope) {\r
127631e0 591\r
45b0fa89
CP
592 if (!idCheck(sID)) {\r
593 return false;\r
5ad04c7b 594 }\r
45b0fa89
CP
595 return sm2.sounds[sID].onposition(nPosition, oMethod, oScope);\r
596\r
127631e0
CP
597 };\r
598\r
45b0fa89
CP
599 // legacy/backwards-compability: lower-case method name\r
600 this.onposition = this.onPosition;\r
601\r
602 /**\r
603 * Calls the clearOnPosition() method of a SMSound object by ID.\r
604 *\r
605 * @param {string} sID The ID of the sound\r
606 * @param {number} nPosition The position to watch for\r
607 * @param {function} oMethod Optional: The relevant callback to fire\r
608 * @return {SMSound} The SMSound object\r
609 */\r
610\r
611 this.clearOnPosition = function(sID, nPosition, oMethod) {\r
612\r
613 if (!idCheck(sID)) {\r
127631e0 614 return false;\r
5ad04c7b 615 }\r
45b0fa89
CP
616 return sm2.sounds[sID].clearOnPosition(nPosition, oMethod);\r
617\r
127631e0
CP
618 };\r
619\r
45b0fa89
CP
620 /**\r
621 * Calls the play() method of a SMSound object by ID.\r
622 *\r
623 * @param {string} sID The ID of the sound\r
624 * @param {object} oOptions Optional: Sound options\r
625 * @return {SMSound} The SMSound object\r
626 */\r
627\r
628 this.play = function(sID, oOptions) {\r
629\r
630 var result = null,\r
631 // legacy function-overloading use case: play('mySound', '/path/to/some.mp3');\r
632 overloaded = (oOptions && !(oOptions instanceof Object));\r
633\r
634 if (!didInit || !sm2.ok()) {\r
635 complain(sm + '.play(): ' + str(!didInit?'notReady':'notOK'));\r
127631e0 636 return false;\r
5ad04c7b 637 }\r
45b0fa89
CP
638\r
639 if (!idCheck(sID, overloaded)) {\r
640\r
641 if (!overloaded) {\r
642 // no sound found for the given ID. Bail.\r
643 return false;\r
5ad04c7b 644 }\r
45b0fa89
CP
645\r
646 if (overloaded) {\r
647 oOptions = {\r
648 url: oOptions\r
649 };\r
5ad04c7b 650 }\r
127631e0 651\r
45b0fa89
CP
652 if (oOptions && oOptions.url) {\r
653 // overloading use case, create+play: .play('someID', {url:'/path/to.mp3'});\r
654 sm2._wD(sm + '.play(): Attempting to create "' + sID + '"', 1);\r
655 oOptions.id = sID;\r
656 result = sm2.createSound(oOptions).play();\r
657 }\r
658\r
659 } else if (overloaded) {\r
660\r
661 // existing sound object case\r
662 oOptions = {\r
663 url: oOptions\r
664 };\r
127631e0 665\r
5ad04c7b 666 }\r
45b0fa89
CP
667\r
668 if (result === null) {\r
669 // default case\r
670 result = sm2.sounds[sID].play(oOptions);\r
5ad04c7b 671 }\r
127631e0 672\r
45b0fa89
CP
673 return result;\r
674\r
127631e0
CP
675 };\r
676\r
45b0fa89
CP
677 this.start = this.play; // just for convenience\r
678\r
679 /**\r
680 * Calls the setPosition() method of a SMSound object by ID.\r
681 *\r
682 * @param {string} sID The ID of the sound\r
683 * @param {number} nMsecOffset Position (milliseconds)\r
684 * @return {SMSound} The SMSound object\r
685 */\r
686\r
687 this.setPosition = function(sID, nMsecOffset) {\r
688\r
689 if (!idCheck(sID)) {\r
690 return false;\r
5ad04c7b 691 }\r
45b0fa89 692 return sm2.sounds[sID].setPosition(nMsecOffset);\r
86f67adc 693\r
127631e0
CP
694 };\r
695\r
45b0fa89
CP
696 /**\r
697 * Calls the stop() method of a SMSound object by ID.\r
698 *\r
699 * @param {string} sID The ID of the sound\r
700 * @return {SMSound} The SMSound object\r
701 */\r
702\r
703 this.stop = function(sID) {\r
704\r
705 if (!idCheck(sID)) {\r
706 return false;\r
5ad04c7b 707 }\r
45b0fa89
CP
708\r
709 sm2._wD(sm + '.stop(' + sID + ')', 1);\r
710 return sm2.sounds[sID].stop();\r
711\r
127631e0
CP
712 };\r
713\r
45b0fa89
CP
714 /**\r
715 * Stops all currently-playing sounds.\r
716 */\r
717\r
718 this.stopAll = function() {\r
719\r
720 var oSound;\r
721 sm2._wD(sm + '.stopAll()', 1);\r
722\r
723 for (oSound in sm2.sounds) {\r
724 if (sm2.sounds.hasOwnProperty(oSound)) {\r
725 // apply only to sound objects\r
726 sm2.sounds[oSound].stop();\r
86f67adc 727 }\r
45b0fa89 728 }\r
86f67adc 729\r
127631e0 730 };\r
127631e0 731\r
45b0fa89
CP
732 /**\r
733 * Calls the pause() method of a SMSound object by ID.\r
734 *\r
735 * @param {string} sID The ID of the sound\r
736 * @return {SMSound} The SMSound object\r
737 */\r
5ad04c7b 738\r
45b0fa89 739 this.pause = function(sID) {\r
86f67adc 740\r
45b0fa89
CP
741 if (!idCheck(sID)) {\r
742 return false;\r
743 }\r
744 return sm2.sounds[sID].pause();\r
127631e0 745\r
127631e0
CP
746 };\r
747\r
45b0fa89
CP
748 /**\r
749 * Pauses all currently-playing sounds.\r
750 */\r
127631e0 751\r
45b0fa89 752 this.pauseAll = function() {\r
127631e0 753\r
45b0fa89
CP
754 var i;\r
755 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
756 sm2.sounds[sm2.soundIDs[i]].pause();\r
757 }\r
86f67adc 758\r
45b0fa89 759 };\r
86f67adc 760\r
45b0fa89
CP
761 /**\r
762 * Calls the resume() method of a SMSound object by ID.\r
763 *\r
764 * @param {string} sID The ID of the sound\r
765 * @return {SMSound} The SMSound object\r
766 */\r
86f67adc 767\r
45b0fa89 768 this.resume = function(sID) {\r
86f67adc 769\r
45b0fa89 770 if (!idCheck(sID)) {\r
127631e0
CP
771 return false;\r
772 }\r
45b0fa89 773 return sm2.sounds[sID].resume();\r
5ad04c7b 774\r
127631e0
CP
775 };\r
776\r
45b0fa89
CP
777 /**\r
778 * Resumes all currently-paused sounds.\r
779 */\r
127631e0 780\r
45b0fa89 781 this.resumeAll = function() {\r
127631e0 782\r
45b0fa89
CP
783 var i;\r
784 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
785 sm2.sounds[sm2.soundIDs[i]].resume();\r
5ad04c7b 786 }\r
45b0fa89 787\r
127631e0
CP
788 };\r
789\r
45b0fa89
CP
790 /**\r
791 * Calls the togglePause() method of a SMSound object by ID.\r
792 *\r
793 * @param {string} sID The ID of the sound\r
794 * @return {SMSound} The SMSound object\r
795 */\r
127631e0 796\r
45b0fa89
CP
797 this.togglePause = function(sID) {\r
798\r
799 if (!idCheck(sID)) {\r
127631e0 800 return false;\r
5ad04c7b 801 }\r
45b0fa89
CP
802 return sm2.sounds[sID].togglePause();\r
803\r
127631e0
CP
804 };\r
805\r
45b0fa89
CP
806 /**\r
807 * Calls the setPan() method of a SMSound object by ID.\r
808 *\r
809 * @param {string} sID The ID of the sound\r
810 * @param {number} nPan The pan value (-100 to 100)\r
811 * @return {SMSound} The SMSound object\r
812 */\r
813\r
814 this.setPan = function(sID, nPan) {\r
815\r
816 if (!idCheck(sID)) {\r
817 return false;\r
5ad04c7b 818 }\r
45b0fa89
CP
819 return sm2.sounds[sID].setPan(nPan);\r
820\r
127631e0
CP
821 };\r
822\r
45b0fa89
CP
823 /**\r
824 * Calls the setVolume() method of a SMSound object by ID.\r
825 *\r
826 * @param {string} sID The ID of the sound\r
827 * @param {number} nVol The volume value (0 to 100)\r
828 * @return {SMSound} The SMSound object\r
829 */\r
830\r
831 this.setVolume = function(sID, nVol) {\r
832\r
833 if (!idCheck(sID)) {\r
834 return false;\r
5ad04c7b 835 }\r
45b0fa89 836 return sm2.sounds[sID].setVolume(nVol);\r
127631e0 837\r
127631e0
CP
838 };\r
839\r
45b0fa89
CP
840 /**\r
841 * Calls the mute() method of either a single SMSound object by ID, or all sound objects.\r
842 *\r
843 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)\r
844 */\r
845\r
846 this.mute = function(sID) {\r
847\r
848 var i = 0;\r
127631e0 849\r
45b0fa89
CP
850 if (sID instanceof String) {\r
851 sID = null;\r
852 }\r
853\r
854 if (!sID) {\r
127631e0 855\r
45b0fa89
CP
856 sm2._wD(sm + '.mute(): Muting all sounds');\r
857 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
858 sm2.sounds[sm2.soundIDs[i]].mute();\r
5ad04c7b 859 }\r
45b0fa89
CP
860 sm2.muted = true;\r
861\r
127631e0 862 } else {\r
45b0fa89
CP
863\r
864 if (!idCheck(sID)) {\r
865 return false;\r
5ad04c7b 866 }\r
45b0fa89
CP
867 sm2._wD(sm + '.mute(): Muting "' + sID + '"');\r
868 return sm2.sounds[sID].mute();\r
869\r
127631e0 870 }\r
45b0fa89
CP
871\r
872 return true;\r
873\r
127631e0
CP
874 };\r
875\r
45b0fa89
CP
876 /**\r
877 * Mutes all sounds.\r
878 */\r
879\r
880 this.muteAll = function() {\r
881\r
882 sm2.mute();\r
883\r
127631e0
CP
884 };\r
885\r
45b0fa89
CP
886 /**\r
887 * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.\r
888 *\r
889 * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)\r
890 */\r
86f67adc 891\r
45b0fa89
CP
892 this.unmute = function(sID) {\r
893\r
894 var i;\r
895\r
896 if (sID instanceof String) {\r
897 sID = null;\r
86f67adc 898 }\r
45b0fa89
CP
899\r
900 if (!sID) {\r
901\r
902 sm2._wD(sm + '.unmute(): Unmuting all sounds');\r
903 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
904 sm2.sounds[sm2.soundIDs[i]].unmute();\r
5ad04c7b 905 }\r
45b0fa89
CP
906 sm2.muted = false;\r
907\r
908 } else {\r
909\r
910 if (!idCheck(sID)) {\r
911 return false;\r
5ad04c7b 912 }\r
45b0fa89
CP
913 sm2._wD(sm + '.unmute(): Unmuting "' + sID + '"');\r
914 return sm2.sounds[sID].unmute();\r
915\r
5ad04c7b 916 }\r
45b0fa89
CP
917\r
918 return true;\r
919\r
127631e0
CP
920 };\r
921\r
45b0fa89
CP
922 /**\r
923 * Unmutes all sounds.\r
924 */\r
925\r
926 this.unmuteAll = function() {\r
927\r
928 sm2.unmute();\r
929\r
127631e0
CP
930 };\r
931\r
45b0fa89
CP
932 /**\r
933 * Calls the toggleMute() method of a SMSound object by ID.\r
934 *\r
935 * @param {string} sID The ID of the sound\r
936 * @return {SMSound} The SMSound object\r
937 */\r
938\r
939 this.toggleMute = function(sID) {\r
940\r
941 if (!idCheck(sID)) {\r
942 return false;\r
5ad04c7b 943 }\r
45b0fa89
CP
944 return sm2.sounds[sID].toggleMute();\r
945\r
127631e0
CP
946 };\r
947\r
45b0fa89
CP
948 /**\r
949 * Retrieves the memory used by the flash plugin.\r
950 *\r
951 * @return {number} The amount of memory in use\r
952 */\r
953\r
954 this.getMemoryUse = function() {\r
955\r
956 // flash-only\r
957 var ram = 0;\r
958\r
959 if (flash && fV !== 8) {\r
960 ram = parseInt(flash._getMemoryUse(), 10);\r
5ad04c7b 961 }\r
45b0fa89
CP
962\r
963 return ram;\r
964\r
127631e0
CP
965 };\r
966\r
45b0fa89
CP
967 /**\r
968 * Undocumented: NOPs soundManager and all SMSound objects.\r
969 */\r
970\r
971 this.disable = function(bNoDisable) {\r
972\r
973 // destroy all functions\r
974 var i;\r
5ad04c7b 975\r
45b0fa89
CP
976 if (bNoDisable === _undefined) {\r
977 bNoDisable = false;\r
127631e0 978 }\r
45b0fa89
CP
979\r
980 if (disabled) {\r
981 return false;\r
982 }\r
983\r
984 disabled = true;\r
985 _wDS('shutdown', 1);\r
986\r
987 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
988 disableObject(sm2.sounds[sm2.soundIDs[i]]);\r
127631e0 989 }\r
86f67adc 990\r
45b0fa89
CP
991 // fire "complete", despite fail\r
992 initComplete(bNoDisable);\r
993 event.remove(window, 'load', initUserOnload);\r
994\r
995 return true;\r
996\r
127631e0
CP
997 };\r
998\r
45b0fa89
CP
999 /**\r
1000 * Determines playability of a MIME type, eg. 'audio/mp3'.\r
1001 */\r
1002\r
1003 this.canPlayMIME = function(sMIME) {\r
1004\r
1005 var result;\r
1006\r
1007 if (sm2.hasHTML5) {\r
1008 result = html5CanPlay({type:sMIME});\r
5ad04c7b 1009 }\r
45b0fa89
CP
1010\r
1011 if (!result && needsFlash) {\r
1012 // if flash 9, test netStream (movieStar) types as well.\r
1013 result = (sMIME && sm2.ok() ? !!((fV > 8 ? sMIME.match(netStreamMimeTypes) : null) || sMIME.match(sm2.mimePattern)) : null); // TODO: make less "weird" (per JSLint)\r
127631e0 1014 }\r
45b0fa89
CP
1015\r
1016 return result;\r
1017\r
127631e0
CP
1018 };\r
1019\r
45b0fa89
CP
1020 /**\r
1021 * Determines playability of a URL based on audio support.\r
1022 *\r
1023 * @param {string} sURL The URL to test\r
1024 * @return {boolean} URL playability\r
1025 */\r
1026\r
1027 this.canPlayURL = function(sURL) {\r
1028\r
1029 var result;\r
1030\r
1031 if (sm2.hasHTML5) {\r
1032 result = html5CanPlay({url: sURL});\r
86f67adc 1033 }\r
45b0fa89
CP
1034\r
1035 if (!result && needsFlash) {\r
1036 result = (sURL && sm2.ok() ? !!(sURL.match(sm2.filePattern)) : null);\r
86f67adc 1037 }\r
86f67adc 1038\r
45b0fa89
CP
1039 return result;\r
1040\r
86f67adc
CP
1041 };\r
1042\r
45b0fa89
CP
1043 /**\r
1044 * Determines playability of an HTML DOM &lt;a&gt; object (or similar object literal) based on audio support.\r
1045 *\r
1046 * @param {object} oLink an HTML DOM &lt;a&gt; object or object literal including href and/or type attributes\r
1047 * @return {boolean} URL playability\r
1048 */\r
127631e0 1049\r
45b0fa89 1050 this.canPlayLink = function(oLink) {\r
5ad04c7b 1051\r
45b0fa89
CP
1052 if (oLink.type !== _undefined && oLink.type) {\r
1053 if (sm2.canPlayMIME(oLink.type)) {\r
1054 return true;\r
1055 }\r
1056 }\r
1057\r
1058 return sm2.canPlayURL(oLink.href);\r
1059\r
1060 };\r
1061\r
1062 /**\r
1063 * Retrieves a SMSound object by ID.\r
1064 *\r
1065 * @param {string} sID The ID of the sound\r
1066 * @return {SMSound} The SMSound object\r
1067 */\r
1068\r
1069 this.getSoundById = function(sID, _suppressDebug) {\r
1070\r
1071 if (!sID) {\r
1072 return null;\r
1073 }\r
1074\r
1075 var result = sm2.sounds[sID];\r
1076\r
1077 // <d>\r
1078 if (!result && !_suppressDebug) {\r
1079 sm2._wD(sm + '.getSoundById(): Sound "' + sID + '" not found.', 2);\r
1080 }\r
1081 // </d>\r
1082\r
1083 return result;\r
1084\r
1085 };\r
1086\r
1087 /**\r
1088 * Queues a callback for execution when SoundManager has successfully initialized.\r
1089 *\r
1090 * @param {function} oMethod The callback method to fire\r
1091 * @param {object} oScope Optional: The scope to apply to the callback\r
1092 */\r
1093\r
1094 this.onready = function(oMethod, oScope) {\r
1095\r
1096 var sType = 'onready',\r
1097 result = false;\r
1098\r
1099 if (typeof oMethod === 'function') {\r
1100\r
1101 // <d>\r
1102 if (didInit) {\r
1103 sm2._wD(str('queue', sType));\r
1104 }\r
1105 // </d>\r
1106\r
1107 if (!oScope) {\r
1108 oScope = window;\r
1109 }\r
1110\r
1111 addOnEvent(sType, oMethod, oScope);\r
1112 processOnEvents();\r
1113\r
1114 result = true;\r
1115\r
1116 } else {\r
1117\r
1118 throw str('needFunction', sType);\r
1119\r
1120 }\r
1121\r
1122 return result;\r
1123\r
1124 };\r
1125\r
1126 /**\r
1127 * Queues a callback for execution when SoundManager has failed to initialize.\r
1128 *\r
1129 * @param {function} oMethod The callback method to fire\r
1130 * @param {object} oScope Optional: The scope to apply to the callback\r
1131 */\r
1132\r
1133 this.ontimeout = function(oMethod, oScope) {\r
1134\r
1135 var sType = 'ontimeout',\r
1136 result = false;\r
1137\r
1138 if (typeof oMethod === 'function') {\r
1139\r
1140 // <d>\r
1141 if (didInit) {\r
1142 sm2._wD(str('queue', sType));\r
1143 }\r
1144 // </d>\r
1145\r
1146 if (!oScope) {\r
1147 oScope = window;\r
1148 }\r
1149\r
1150 addOnEvent(sType, oMethod, oScope);\r
1151 processOnEvents({type:sType});\r
1152\r
1153 result = true;\r
1154\r
1155 } else {\r
1156\r
1157 throw str('needFunction', sType);\r
1158\r
1159 }\r
1160\r
1161 return result;\r
1162\r
1163 };\r
1164\r
1165 /**\r
1166 * Writes console.log()-style debug output to a console or in-browser element.\r
1167 * Applies when debugMode = true\r
1168 *\r
1169 * @param {string} sText The console message\r
1170 * @param {object} nType Optional log level (number), or object. Number case: Log type/style where 0 = 'info', 1 = 'warn', 2 = 'error'. Object case: Object to be dumped.\r
1171 */\r
1172\r
1173 this._writeDebug = function(sText, sTypeOrObject) {\r
1174\r
1175 // pseudo-private console.log()-style output\r
1176 // <d>\r
1177\r
1178 var sDID = 'soundmanager-debug', o, oItem;\r
1179\r
1180 if (!sm2.debugMode) {\r
1181 return false;\r
1182 }\r
1183\r
1184 if (hasConsole && sm2.useConsole) {\r
1185 if (sTypeOrObject && typeof sTypeOrObject === 'object') {\r
1186 // object passed; dump to console.\r
1187 console.log(sText, sTypeOrObject);\r
1188 } else if (debugLevels[sTypeOrObject] !== _undefined) {\r
1189 console[debugLevels[sTypeOrObject]](sText);\r
1190 } else {\r
1191 console.log(sText);\r
1192 }\r
1193 if (sm2.consoleOnly) {\r
1194 return true;\r
1195 }\r
1196 }\r
1197\r
1198 o = id(sDID);\r
1199\r
1200 if (!o) {\r
1201 return false;\r
1202 }\r
1203\r
1204 oItem = doc.createElement('div');\r
1205\r
1206 if (++wdCount % 2 === 0) {\r
1207 oItem.className = 'sm2-alt';\r
1208 }\r
1209\r
1210 if (sTypeOrObject === _undefined) {\r
1211 sTypeOrObject = 0;\r
1212 } else {\r
1213 sTypeOrObject = parseInt(sTypeOrObject, 10);\r
1214 }\r
1215\r
1216 oItem.appendChild(doc.createTextNode(sText));\r
1217\r
1218 if (sTypeOrObject) {\r
1219 if (sTypeOrObject >= 2) {\r
1220 oItem.style.fontWeight = 'bold';\r
1221 }\r
1222 if (sTypeOrObject === 3) {\r
1223 oItem.style.color = '#ff3333';\r
1224 }\r
1225 }\r
1226\r
1227 // top-to-bottom\r
1228 // o.appendChild(oItem);\r
1229\r
1230 // bottom-to-top\r
1231 o.insertBefore(oItem, o.firstChild);\r
1232\r
1233 o = null;\r
1234 // </d>\r
1235\r
1236 return true;\r
1237\r
1238 };\r
1239\r
1240 // <d>\r
1241 // last-resort debugging option\r
1242 if (wl.indexOf('sm2-debug=alert') !== -1) {\r
1243 this._writeDebug = function(sText) {\r
1244 window.alert(sText);\r
1245 };\r
1246 }\r
1247 // </d>\r
1248\r
1249 // alias\r
1250 this._wD = this._writeDebug;\r
1251\r
1252 /**\r
1253 * Provides debug / state information on all SMSound objects.\r
1254 */\r
1255\r
1256 this._debug = function() {\r
1257\r
1258 // <d>\r
1259 var i, j;\r
1260 _wDS('currentObj', 1);\r
1261\r
1262 for (i = 0, j = sm2.soundIDs.length; i < j; i++) {\r
1263 sm2.sounds[sm2.soundIDs[i]]._debug();\r
1264 }\r
1265 // </d>\r
1266\r
1267 };\r
1268\r
1269 /**\r
1270 * Restarts and re-initializes the SoundManager instance.\r
1271 *\r
1272 * @param {boolean} resetEvents Optional: When true, removes all registered onready and ontimeout event callbacks.\r
1273 * @param {boolean} excludeInit Options: When true, does not call beginDelayedInit() (which would restart SM2).\r
1274 * @return {object} soundManager The soundManager instance.\r
1275 */\r
1276\r
1277 this.reboot = function(resetEvents, excludeInit) {\r
1278\r
1279 // reset some (or all) state, and re-init unless otherwise specified.\r
1280\r
1281 // <d>\r
1282 if (sm2.soundIDs.length) {\r
1283 sm2._wD('Destroying ' + sm2.soundIDs.length + ' SMSound object' + (sm2.soundIDs.length !== 1 ? 's' : '') + '...');\r
1284 }\r
1285 // </d>\r
1286\r
1287 var i, j, k;\r
1288\r
1289 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
1290 sm2.sounds[sm2.soundIDs[i]].destruct();\r
1291 }\r
1292\r
1293 // trash ze flash (remove from the DOM)\r
1294\r
1295 if (flash) {\r
1296\r
1297 try {\r
1298\r
1299 if (isIE) {\r
1300 oRemovedHTML = flash.innerHTML;\r
1301 }\r
1302\r
1303 oRemoved = flash.parentNode.removeChild(flash);\r
1304\r
1305 } catch(e) {\r
1306\r
1307 // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.\r
1308\r
1309 _wDS('badRemove', 2);\r
1310\r
1311 }\r
1312\r
1313 }\r
1314\r
1315 // actually, force recreate of movie.\r
1316\r
1317 oRemovedHTML = oRemoved = needsFlash = flash = null;\r
1318\r
1319 sm2.enabled = didDCLoaded = didInit = waitingForEI = initPending = didAppend = appendSuccess = disabled = useGlobalHTML5Audio = sm2.swfLoaded = false;\r
1320\r
1321 sm2.soundIDs = [];\r
1322 sm2.sounds = {};\r
1323\r
1324 idCounter = 0;\r
1325\r
1326 if (!resetEvents) {\r
1327 // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init\r
1328 for (i in on_queue) {\r
1329 if (on_queue.hasOwnProperty(i)) {\r
1330 for (j = 0, k = on_queue[i].length; j < k; j++) {\r
1331 on_queue[i][j].fired = false;\r
1332 }\r
1333 }\r
1334 }\r
1335 } else {\r
1336 // remove all callbacks entirely\r
1337 on_queue = [];\r
1338 }\r
1339\r
1340 // <d>\r
1341 if (!excludeInit) {\r
1342 sm2._wD(sm + ': Rebooting...');\r
1343 }\r
1344 // </d>\r
1345\r
1346 // reset HTML5 and flash canPlay test results\r
1347\r
1348 sm2.html5 = {\r
1349 'usingFlash': null\r
1350 };\r
1351\r
1352 sm2.flash = {};\r
1353\r
1354 // reset device-specific HTML/flash mode switches\r
1355\r
1356 sm2.html5Only = false;\r
1357 sm2.ignoreFlash = false;\r
1358\r
1359 window.setTimeout(function() {\r
1360\r
1361 preInit();\r
1362\r
1363 // by default, re-init\r
1364\r
1365 if (!excludeInit) {\r
1366 sm2.beginDelayedInit();\r
1367 }\r
1368\r
1369 }, 20);\r
1370\r
1371 return sm2;\r
1372\r
1373 };\r
1374\r
1375 this.reset = function() {\r
1376\r
1377 /**\r
1378 * Shuts down and restores the SoundManager instance to its original loaded state, without an explicit reboot. All onready/ontimeout handlers are removed.\r
1379 * After this call, SM2 may be re-initialized via soundManager.beginDelayedInit().\r
1380 * @return {object} soundManager The soundManager instance.\r
1381 */\r
1382\r
1383 _wDS('reset');\r
1384 return sm2.reboot(true, true);\r
1385\r
1386 };\r
1387\r
1388 /**\r
1389 * Undocumented: Determines the SM2 flash movie's load progress.\r
1390 *\r
1391 * @return {number or null} Percent loaded, or if invalid/unsupported, null.\r
1392 */\r
1393\r
1394 this.getMoviePercent = function() {\r
1395\r
1396 /**\r
1397 * Interesting syntax notes...\r
1398 * Flash/ExternalInterface (ActiveX/NPAPI) bridge methods are not typeof "function" nor instanceof Function, but are still valid.\r
1399 * Additionally, JSLint dislikes ('PercentLoaded' in flash)-style syntax and recommends hasOwnProperty(), which does not work in this case.\r
1400 * Furthermore, using (flash && flash.PercentLoaded) causes IE to throw "object doesn't support this property or method".\r
1401 * Thus, 'in' syntax must be used.\r
1402 */\r
1403\r
1404 return (flash && 'PercentLoaded' in flash ? flash.PercentLoaded() : null); // Yes, JSLint. See nearby comment in source for explanation.\r
1405\r
1406 };\r
1407\r
1408 /**\r
1409 * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().\r
1410 */\r
1411\r
1412 this.beginDelayedInit = function() {\r
1413\r
1414 windowLoaded = true;\r
1415 domContentLoaded();\r
1416\r
1417 setTimeout(function() {\r
1418\r
1419 if (initPending) {\r
1420 return false;\r
1421 }\r
1422\r
1423 createMovie();\r
1424 initMovie();\r
1425 initPending = true;\r
1426\r
1427 return true;\r
1428\r
1429 }, 20);\r
1430\r
1431 delayWaitForEI();\r
1432\r
1433 };\r
1434\r
1435 /**\r
1436 * Destroys the SoundManager instance and all SMSound instances.\r
1437 */\r
1438\r
1439 this.destruct = function() {\r
1440\r
1441 sm2._wD(sm + '.destruct()');\r
1442 sm2.disable(true);\r
1443\r
1444 };\r
1445\r
1446 /**\r
1447 * SMSound() (sound object) constructor\r
1448 * ------------------------------------\r
1449 *\r
1450 * @param {object} oOptions Sound options (id and url are required attributes)\r
1451 * @return {SMSound} The new SMSound object\r
1452 */\r
1453\r
1454 SMSound = function(oOptions) {\r
1455\r
1456 var s = this, resetProperties, add_html5_events, remove_html5_events, stop_html5_timer, start_html5_timer, attachOnPosition, onplay_called = false, onPositionItems = [], onPositionFired = 0, detachOnPosition, applyFromTo, lastURL = null, lastHTML5State, urlOmitted;\r
1457\r
1458 lastHTML5State = {\r
1459 // tracks duration + position (time)\r
1460 duration: null,\r
1461 time: null\r
1462 };\r
1463\r
1464 this.id = oOptions.id;\r
1465\r
1466 // legacy\r
1467 this.sID = this.id;\r
1468\r
1469 this.url = oOptions.url;\r
1470 this.options = mixin(oOptions);\r
1471\r
1472 // per-play-instance-specific options\r
1473 this.instanceOptions = this.options;\r
1474\r
1475 // short alias\r
1476 this._iO = this.instanceOptions;\r
1477\r
1478 // assign property defaults\r
1479 this.pan = this.options.pan;\r
1480 this.volume = this.options.volume;\r
1481\r
1482 // whether or not this object is using HTML5\r
1483 this.isHTML5 = false;\r
1484\r
1485 // internal HTML5 Audio() object reference\r
1486 this._a = null;\r
1487\r
1488 // for flash 8 special-case createSound() without url, followed by load/play with url case\r
1489 urlOmitted = (this.url ? false : true);\r
1490\r
1491 /**\r
1492 * SMSound() public methods\r
1493 * ------------------------\r
1494 */\r
1495\r
1496 this.id3 = {};\r
1497\r
1498 /**\r
1499 * Writes SMSound object parameters to debug console\r
1500 */\r
1501\r
1502 this._debug = function() {\r
1503\r
1504 // <d>\r
1505 sm2._wD(s.id + ': Merged options:', s.options);\r
1506 // </d>\r
1507\r
1508 };\r
1509\r
1510 /**\r
1511 * Begins loading a sound per its *url*.\r
1512 *\r
1513 * @param {object} oOptions Optional: Sound options\r
1514 * @return {SMSound} The SMSound object\r
1515 */\r
1516\r
1517 this.load = function(oOptions) {\r
1518\r
1519 var oSound = null, instanceOptions;\r
1520\r
1521 if (oOptions !== _undefined) {\r
1522 s._iO = mixin(oOptions, s.options);\r
1523 } else {\r
1524 oOptions = s.options;\r
1525 s._iO = oOptions;\r
1526 if (lastURL && lastURL !== s.url) {\r
1527 _wDS('manURL');\r
1528 s._iO.url = s.url;\r
1529 s.url = null;\r
1530 }\r
1531 }\r
1532\r
1533 if (!s._iO.url) {\r
1534 s._iO.url = s.url;\r
1535 }\r
1536\r
1537 s._iO.url = parseURL(s._iO.url);\r
1538\r
1539 // ensure we're in sync\r
1540 s.instanceOptions = s._iO;\r
1541\r
1542 // local shortcut\r
1543 instanceOptions = s._iO;\r
1544\r
1545 sm2._wD(s.id + ': load (' + instanceOptions.url + ')');\r
1546\r
1547 if (!instanceOptions.url && !s.url) {\r
1548 sm2._wD(s.id + ': load(): url is unassigned. Exiting.', 2);\r
1549 return s;\r
1550 }\r
1551\r
1552 // <d>\r
1553 if (!s.isHTML5 && fV === 8 && !s.url && !instanceOptions.autoPlay) {\r
1554 // flash 8 load() -> play() won't work before onload has fired.\r
1555 sm2._wD(s.id + ': Flash 8 load() limitation: Wait for onload() before calling play().', 1);\r
1556 }\r
1557 // </d>\r
1558\r
1559 if (instanceOptions.url === s.url && s.readyState !== 0 && s.readyState !== 2) {\r
1560 _wDS('onURL', 1);\r
1561 // if loaded and an onload() exists, fire immediately.\r
1562 if (s.readyState === 3 && instanceOptions.onload) {\r
1563 // assume success based on truthy duration.\r
1564 wrapCallback(s, function() {\r
1565 instanceOptions.onload.apply(s, [(!!s.duration)]);\r
1566 });\r
1567 }\r
1568 return s;\r
1569 }\r
1570\r
1571 // reset a few state properties\r
1572\r
1573 s.loaded = false;\r
1574 s.readyState = 1;\r
1575 s.playState = 0;\r
1576 s.id3 = {};\r
1577\r
1578 // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.\r
1579\r
1580 if (html5OK(instanceOptions)) {\r
1581\r
1582 oSound = s._setup_html5(instanceOptions);\r
1583\r
1584 if (!oSound._called_load) {\r
1585\r
1586 s._html5_canplay = false;\r
1587\r
1588 // TODO: review called_load / html5_canplay logic\r
1589\r
1590 // if url provided directly to load(), assign it here.\r
1591\r
1592 if (s.url !== instanceOptions.url) {\r
1593\r
1594 sm2._wD(_wDS('manURL') + ': ' + instanceOptions.url);\r
1595\r
1596 s._a.src = instanceOptions.url;\r
1597\r
1598 // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)\r
1599\r
1600 // reset position for new URL\r
1601 s.setPosition(0);\r
1602\r
1603 }\r
1604\r
1605 // given explicit load call, try to preload.\r
1606\r
1607 // early HTML5 implementation (non-standard)\r
1608 s._a.autobuffer = 'auto';\r
1609\r
1610 // standard property, values: none / metadata / auto\r
1611 // reference: http://msdn.microsoft.com/en-us/library/ie/ff974759%28v=vs.85%29.aspx\r
1612 s._a.preload = 'auto';\r
1613\r
1614 s._a._called_load = true;\r
1615\r
1616 } else {\r
1617\r
1618 sm2._wD(s.id + ': Ignoring request to load again');\r
1619\r
1620 }\r
1621\r
1622 } else {\r
1623\r
1624 if (sm2.html5Only) {\r
1625 sm2._wD(s.id + ': No flash support. Exiting.');\r
1626 return s;\r
1627 }\r
1628\r
1629 if (s._iO.url && s._iO.url.match(/data\:/i)) {\r
1630 // data: URIs not supported by Flash, either.\r
1631 sm2._wD(s.id + ': data: URIs not supported via Flash. Exiting.');\r
1632 return s;\r
1633 }\r
1634\r
1635 try {\r
1636 s.isHTML5 = false;\r
1637 s._iO = policyFix(loopFix(instanceOptions));\r
1638 // re-assign local shortcut\r
1639 instanceOptions = s._iO;\r
1640 if (fV === 8) {\r
1641 flash._load(s.id, instanceOptions.url, instanceOptions.stream, instanceOptions.autoPlay, instanceOptions.usePolicyFile);\r
1642 } else {\r
1643 flash._load(s.id, instanceOptions.url, !!(instanceOptions.stream), !!(instanceOptions.autoPlay), instanceOptions.loops||1, !!(instanceOptions.autoLoad), instanceOptions.usePolicyFile);\r
1644 }\r
1645 } catch(e) {\r
1646 _wDS('smError', 2);\r
1647 debugTS('onload', false);\r
1648 catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});\r
1649 }\r
1650\r
1651 }\r
1652\r
1653 // after all of this, ensure sound url is up to date.\r
1654 s.url = instanceOptions.url;\r
1655\r
1656 return s;\r
1657\r
1658 };\r
1659\r
1660 /**\r
1661 * Unloads a sound, canceling any open HTTP requests.\r
1662 *\r
1663 * @return {SMSound} The SMSound object\r
1664 */\r
1665\r
1666 this.unload = function() {\r
1667\r
1668 // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL\r
1669 // Flash 9/AS3: Close stream, preventing further load\r
1670 // HTML5: Most UAs will use empty URL\r
1671\r
1672 if (s.readyState !== 0) {\r
1673\r
1674 sm2._wD(s.id + ': unload()');\r
1675\r
1676 if (!s.isHTML5) {\r
1677\r
1678 if (fV === 8) {\r
1679 flash._unload(s.id, emptyURL);\r
1680 } else {\r
1681 flash._unload(s.id);\r
1682 }\r
1683\r
1684 } else {\r
1685\r
1686 stop_html5_timer();\r
1687\r
1688 if (s._a) {\r
1689\r
1690 s._a.pause();\r
1691\r
1692 // update empty URL, too\r
1693 lastURL = html5Unload(s._a);\r
1694\r
1695 }\r
1696\r
1697 }\r
1698\r
1699 // reset load/status flags\r
1700 resetProperties();\r
1701\r
1702 }\r
1703\r
1704 return s;\r
1705\r
1706 };\r
1707\r
1708 /**\r
1709 * Unloads and destroys a sound.\r
1710 */\r
1711\r
1712 this.destruct = function(_bFromSM) {\r
1713\r
1714 sm2._wD(s.id + ': Destruct');\r
1715\r
1716 if (!s.isHTML5) {\r
1717\r
1718 // kill sound within Flash\r
1719 // Disable the onfailure handler\r
1720 s._iO.onfailure = null;\r
1721 flash._destroySound(s.id);\r
1722\r
1723 } else {\r
1724\r
1725 stop_html5_timer();\r
1726\r
1727 if (s._a) {\r
1728 s._a.pause();\r
1729 html5Unload(s._a);\r
1730 if (!useGlobalHTML5Audio) {\r
1731 remove_html5_events();\r
1732 }\r
1733 // break obvious circular reference\r
1734 s._a._s = null;\r
1735 s._a = null;\r
1736 }\r
1737\r
1738 }\r
1739\r
1740 if (!_bFromSM) {\r
1741 // ensure deletion from controller\r
1742 sm2.destroySound(s.id, true);\r
1743 }\r
1744\r
1745 };\r
1746\r
1747 /**\r
1748 * Begins playing a sound.\r
1749 *\r
1750 * @param {object} oOptions Optional: Sound options\r
1751 * @return {SMSound} The SMSound object\r
1752 */\r
1753\r
1754 this.play = function(oOptions, _updatePlayState) {\r
1755\r
1756 var fN, allowMulti, a, onready,\r
1757 audioClone, onended, oncanplay,\r
1758 startOK = true,\r
1759 exit = null;\r
1760\r
1761 // <d>\r
1762 fN = s.id + ': play(): ';\r
1763 // </d>\r
1764\r
1765 // default to true\r
1766 _updatePlayState = (_updatePlayState === _undefined ? true : _updatePlayState);\r
1767\r
1768 if (!oOptions) {\r
1769 oOptions = {};\r
1770 }\r
1771\r
1772 // first, use local URL (if specified)\r
1773 if (s.url) {\r
1774 s._iO.url = s.url;\r
1775 }\r
1776\r
1777 // mix in any options defined at createSound()\r
1778 s._iO = mixin(s._iO, s.options);\r
1779\r
1780 // mix in any options specific to this method\r
1781 s._iO = mixin(oOptions, s._iO);\r
1782\r
1783 s._iO.url = parseURL(s._iO.url);\r
1784\r
1785 s.instanceOptions = s._iO;\r
1786\r
1787 // RTMP-only\r
1788 if (!s.isHTML5 && s._iO.serverURL && !s.connected) {\r
1789 if (!s.getAutoPlay()) {\r
1790 sm2._wD(fN +' Netstream not connected yet - setting autoPlay');\r
1791 s.setAutoPlay(true);\r
1792 }\r
1793 // play will be called in onconnect()\r
1794 return s;\r
1795 }\r
1796\r
1797 if (html5OK(s._iO)) {\r
1798 s._setup_html5(s._iO);\r
1799 start_html5_timer();\r
1800 }\r
1801\r
1802 if (s.playState === 1 && !s.paused) {\r
1803 allowMulti = s._iO.multiShot;\r
1804 if (!allowMulti) {\r
1805 sm2._wD(fN + 'Already playing (one-shot)', 1);\r
1806 if (s.isHTML5) {\r
1807 // go back to original position.\r
1808 s.setPosition(s._iO.position);\r
1809 }\r
1810 exit = s;\r
1811 } else {\r
1812 sm2._wD(fN + 'Already playing (multi-shot)', 1);\r
1813 }\r
1814 }\r
1815\r
1816 if (exit !== null) {\r
1817 return exit;\r
1818 }\r
1819\r
1820 // edge case: play() with explicit URL parameter\r
1821 if (oOptions.url && oOptions.url !== s.url) {\r
1822\r
1823 // special case for createSound() followed by load() / play() with url; avoid double-load case.\r
1824 if (!s.readyState && !s.isHTML5 && fV === 8 && urlOmitted) {\r
1825\r
1826 urlOmitted = false;\r
1827\r
1828 } else {\r
1829\r
1830 // load using merged options\r
1831 s.load(s._iO);\r
1832\r
1833 }\r
1834\r
1835 }\r
1836\r
1837 if (!s.loaded) {\r
1838\r
1839 if (s.readyState === 0) {\r
1840\r
1841 sm2._wD(fN + 'Attempting to load');\r
1842\r
1843 // try to get this sound playing ASAP\r
1844 if (!s.isHTML5 && !sm2.html5Only) {\r
1845\r
1846 // flash: assign directly because setAutoPlay() increments the instanceCount\r
1847 s._iO.autoPlay = true;\r
1848 s.load(s._iO);\r
1849\r
1850 } else if (s.isHTML5) {\r
1851\r
1852 // iOS needs this when recycling sounds, loading a new URL on an existing object.\r
1853 s.load(s._iO);\r
1854\r
1855 } else {\r
1856\r
1857 sm2._wD(fN + 'Unsupported type. Exiting.');\r
1858 exit = s;\r
1859\r
1860 }\r
1861\r
1862 // HTML5 hack - re-set instanceOptions?\r
1863 s.instanceOptions = s._iO;\r
1864\r
1865 } else if (s.readyState === 2) {\r
1866\r
1867 sm2._wD(fN + 'Could not load - exiting', 2);\r
1868 exit = s;\r
1869\r
1870 } else {\r
1871\r
1872 sm2._wD(fN + 'Loading - attempting to play...');\r
1873\r
1874 }\r
1875\r
1876 } else {\r
1877\r
1878 // "play()"\r
1879 sm2._wD(fN.substr(0, fN.lastIndexOf(':')));\r
1880\r
1881 }\r
1882\r
1883 if (exit !== null) {\r
1884 return exit;\r
1885 }\r
1886\r
1887 if (!s.isHTML5 && fV === 9 && s.position > 0 && s.position === s.duration) {\r
1888 // flash 9 needs a position reset if play() is called while at the end of a sound.\r
1889 sm2._wD(fN + 'Sound at end, resetting to position:0');\r
1890 oOptions.position = 0;\r
1891 }\r
1892\r
1893 /**\r
1894 * Streams will pause when their buffer is full if they are being loaded.\r
1895 * In this case paused is true, but the song hasn't started playing yet.\r
1896 * If we just call resume() the onplay() callback will never be called.\r
1897 * So only call resume() if the position is > 0.\r
1898 * Another reason is because options like volume won't have been applied yet.\r
1899 * For normal sounds, just resume.\r
1900 */\r
1901\r
1902 if (s.paused && s.position >= 0 && (!s._iO.serverURL || s.position > 0)) {\r
1903\r
1904 // https://gist.github.com/37b17df75cc4d7a90bf6\r
1905 sm2._wD(fN + 'Resuming from paused state', 1);\r
1906 s.resume();\r
1907\r
1908 } else {\r
1909\r
1910 s._iO = mixin(oOptions, s._iO);\r
1911\r
1912 // apply from/to parameters, if they exist (and not using RTMP)\r
1913 if (s._iO.from !== null && s._iO.to !== null && s.instanceCount === 0 && s.playState === 0 && !s._iO.serverURL) {\r
1914\r
1915 onready = function() {\r
1916 // sound "canplay" or onload()\r
1917 // re-apply from/to to instance options, and start playback\r
1918 s._iO = mixin(oOptions, s._iO);\r
1919 s.play(s._iO);\r
1920 };\r
1921\r
1922 // HTML5 needs to at least have "canplay" fired before seeking.\r
1923 if (s.isHTML5 && !s._html5_canplay) {\r
1924\r
1925 // this hasn't been loaded yet. load it first, and then do this again.\r
1926 sm2._wD(fN + 'Beginning load for from/to case');\r
1927\r
1928 s.load({\r
1929 // note: custom HTML5-only event added for from/to implementation.\r
1930 _oncanplay: onready\r
1931 });\r
1932\r
1933 exit = false;\r
1934\r
1935 } else if (!s.isHTML5 && !s.loaded && (!s.readyState || s.readyState !== 2)) {\r
1936\r
1937 // to be safe, preload the whole thing in Flash.\r
1938\r
1939 sm2._wD(fN + 'Preloading for from/to case');\r
1940\r
1941 s.load({\r
1942 onload: onready\r
1943 });\r
1944\r
1945 exit = false;\r
1946\r
1947 }\r
1948\r
1949 if (exit !== null) {\r
1950 return exit;\r
1951 }\r
1952\r
1953 // otherwise, we're ready to go. re-apply local options, and continue\r
1954\r
1955 s._iO = applyFromTo();\r
1956\r
1957 }\r
1958\r
1959 // sm2._wD(fN + 'Starting to play');\r
1960\r
1961 // increment instance counter, where enabled + supported\r
1962 if (!s.instanceCount || s._iO.multiShotEvents || (s.isHTML5 && s._iO.multiShot && !useGlobalHTML5Audio) || (!s.isHTML5 && fV > 8 && !s.getAutoPlay())) {\r
1963 s.instanceCount++;\r
1964 }\r
1965\r
1966 // if first play and onposition parameters exist, apply them now\r
1967 if (s._iO.onposition && s.playState === 0) {\r
1968 attachOnPosition(s);\r
1969 }\r
1970\r
1971 s.playState = 1;\r
1972 s.paused = false;\r
1973\r
1974 s.position = (s._iO.position !== _undefined && !isNaN(s._iO.position) ? s._iO.position : 0);\r
1975\r
1976 if (!s.isHTML5) {\r
1977 s._iO = policyFix(loopFix(s._iO));\r
1978 }\r
1979\r
1980 if (s._iO.onplay && _updatePlayState) {\r
1981 s._iO.onplay.apply(s);\r
1982 onplay_called = true;\r
1983 }\r
1984\r
1985 s.setVolume(s._iO.volume, true);\r
1986 s.setPan(s._iO.pan, true);\r
1987\r
1988 if (!s.isHTML5) {\r
1989\r
1990 startOK = flash._start(s.id, s._iO.loops || 1, (fV === 9 ? s.position : s.position / msecScale), s._iO.multiShot || false);\r
1991\r
1992 if (fV === 9 && !startOK) {\r
1993 // edge case: no sound hardware, or 32-channel flash ceiling hit.\r
1994 // applies only to Flash 9, non-NetStream/MovieStar sounds.\r
1995 // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29\r
1996 sm2._wD(fN + 'No sound hardware, or 32-sound ceiling hit', 2);\r
1997 if (s._iO.onplayerror) {\r
1998 s._iO.onplayerror.apply(s);\r
1999 }\r
2000\r
2001 }\r
2002\r
2003 } else {\r
2004\r
2005 if (s.instanceCount < 2) {\r
2006\r
2007 // HTML5 single-instance case\r
2008\r
2009 start_html5_timer();\r
2010\r
2011 a = s._setup_html5();\r
2012\r
2013 s.setPosition(s._iO.position);\r
2014\r
2015 a.play();\r
2016\r
2017 } else {\r
2018\r
2019 // HTML5 multi-shot case\r
2020\r
2021 sm2._wD(s.id + ': Cloning Audio() for instance #' + s.instanceCount + '...');\r
2022\r
2023 audioClone = new Audio(s._iO.url);\r
2024\r
2025 onended = function() {\r
2026 event.remove(audioClone, 'ended', onended);\r
2027 s._onfinish(s);\r
2028 // cleanup\r
2029 html5Unload(audioClone);\r
2030 audioClone = null;\r
2031 };\r
2032\r
2033 oncanplay = function() {\r
2034 event.remove(audioClone, 'canplay', oncanplay);\r
2035 try {\r
2036 audioClone.currentTime = s._iO.position/msecScale;\r
2037 } catch(err) {\r
2038 complain(s.id + ': multiShot play() failed to apply position of ' + (s._iO.position/msecScale));\r
2039 }\r
2040 audioClone.play();\r
2041 };\r
2042\r
2043 event.add(audioClone, 'ended', onended);\r
2044\r
2045 // apply volume to clones, too\r
2046 if (s._iO.volume !== undefined) {\r
2047 audioClone.volume = Math.max(0, Math.min(1, s._iO.volume/100));\r
2048 }\r
2049\r
2050 // playing multiple muted sounds? if you do this, you're weird ;) - but let's cover it.\r
2051 if (s.muted) {\r
2052 audioClone.muted = true;\r
2053 }\r
2054\r
2055 if (s._iO.position) {\r
2056 // HTML5 audio can't seek before onplay() event has fired.\r
2057 // wait for canplay, then seek to position and start playback.\r
2058 event.add(audioClone, 'canplay', oncanplay);\r
2059 } else {\r
2060 // begin playback at currentTime: 0\r
2061 audioClone.play();\r
2062 }\r
2063\r
2064 }\r
2065\r
2066 }\r
2067\r
2068 }\r
2069\r
2070 return s;\r
2071\r
2072 };\r
2073\r
2074 // just for convenience\r
2075 this.start = this.play;\r
2076\r
2077 /**\r
2078 * Stops playing a sound (and optionally, all sounds)\r
2079 *\r
2080 * @param {boolean} bAll Optional: Whether to stop all sounds\r
2081 * @return {SMSound} The SMSound object\r
2082 */\r
2083\r
2084 this.stop = function(bAll) {\r
2085\r
2086 var instanceOptions = s._iO,\r
2087 originalPosition;\r
2088\r
2089 if (s.playState === 1) {\r
2090\r
2091 sm2._wD(s.id + ': stop()');\r
2092\r
2093 s._onbufferchange(0);\r
2094 s._resetOnPosition(0);\r
2095 s.paused = false;\r
2096\r
2097 if (!s.isHTML5) {\r
2098 s.playState = 0;\r
2099 }\r
2100\r
2101 // remove onPosition listeners, if any\r
2102 detachOnPosition();\r
2103\r
2104 // and "to" position, if set\r
2105 if (instanceOptions.to) {\r
2106 s.clearOnPosition(instanceOptions.to);\r
2107 }\r
2108\r
2109 if (!s.isHTML5) {\r
2110\r
2111 flash._stop(s.id, bAll);\r
2112\r
2113 // hack for netStream: just unload\r
2114 if (instanceOptions.serverURL) {\r
2115 s.unload();\r
2116 }\r
2117\r
2118 } else {\r
2119\r
2120 if (s._a) {\r
2121\r
2122 originalPosition = s.position;\r
2123\r
2124 // act like Flash, though\r
2125 s.setPosition(0);\r
2126\r
2127 // hack: reflect old position for onstop() (also like Flash)\r
2128 s.position = originalPosition;\r
2129\r
2130 // html5 has no stop()\r
2131 // NOTE: pausing means iOS requires interaction to resume.\r
2132 s._a.pause();\r
2133\r
2134 s.playState = 0;\r
2135\r
2136 // and update UI\r
2137 s._onTimer();\r
2138\r
2139 stop_html5_timer();\r
2140\r
2141 }\r
2142\r
2143 }\r
2144\r
2145 s.instanceCount = 0;\r
2146 s._iO = {};\r
2147\r
2148 if (instanceOptions.onstop) {\r
2149 instanceOptions.onstop.apply(s);\r
2150 }\r
2151\r
2152 }\r
2153\r
2154 return s;\r
2155\r
2156 };\r
2157\r
2158 /**\r
2159 * Undocumented/internal: Sets autoPlay for RTMP.\r
2160 *\r
2161 * @param {boolean} autoPlay state\r
2162 */\r
2163\r
2164 this.setAutoPlay = function(autoPlay) {\r
2165\r
2166 sm2._wD(s.id + ': Autoplay turned ' + (autoPlay ? 'on' : 'off'));\r
2167 s._iO.autoPlay = autoPlay;\r
2168\r
2169 if (!s.isHTML5) {\r
2170 flash._setAutoPlay(s.id, autoPlay);\r
2171 if (autoPlay) {\r
2172 // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)\r
2173 if (!s.instanceCount && s.readyState === 1) {\r
2174 s.instanceCount++;\r
2175 sm2._wD(s.id + ': Incremented instance count to '+s.instanceCount);\r
2176 }\r
2177 }\r
2178 }\r
2179\r
2180 };\r
2181\r
2182 /**\r
2183 * Undocumented/internal: Returns the autoPlay boolean.\r
2184 *\r
2185 * @return {boolean} The current autoPlay value\r
2186 */\r
2187\r
2188 this.getAutoPlay = function() {\r
2189\r
2190 return s._iO.autoPlay;\r
2191\r
2192 };\r
2193\r
2194 /**\r
2195 * Sets the position of a sound.\r
2196 *\r
2197 * @param {number} nMsecOffset Position (milliseconds)\r
2198 * @return {SMSound} The SMSound object\r
2199 */\r
2200\r
2201 this.setPosition = function(nMsecOffset) {\r
2202\r
2203 if (nMsecOffset === _undefined) {\r
2204 nMsecOffset = 0;\r
2205 }\r
2206\r
2207 var position, position1K,\r
2208 // Use the duration from the instance options, if we don't have a track duration yet.\r
2209 // position >= 0 and <= current available (loaded) duration\r
2210 offset = (s.isHTML5 ? Math.max(nMsecOffset, 0) : Math.min(s.duration || s._iO.duration, Math.max(nMsecOffset, 0)));\r
2211\r
2212 s.position = offset;\r
2213 position1K = s.position/msecScale;\r
2214 s._resetOnPosition(s.position);\r
2215 s._iO.position = offset;\r
2216\r
2217 if (!s.isHTML5) {\r
2218\r
2219 position = (fV === 9 ? s.position : position1K);\r
2220\r
2221 if (s.readyState && s.readyState !== 2) {\r
2222 // if paused or not playing, will not resume (by playing)\r
2223 flash._setPosition(s.id, position, (s.paused || !s.playState), s._iO.multiShot);\r
2224 }\r
2225\r
2226 } else if (s._a) {\r
2227\r
2228 // Set the position in the canplay handler if the sound is not ready yet\r
2229 if (s._html5_canplay) {\r
2230\r
2231 if (s._a.currentTime !== position1K) {\r
2232\r
2233 /**\r
2234 * DOM/JS errors/exceptions to watch out for:\r
2235 * if seek is beyond (loaded?) position, "DOM exception 11"\r
2236 * "INDEX_SIZE_ERR": DOM exception 1\r
2237 */\r
2238 sm2._wD(s.id + ': setPosition('+position1K+')');\r
2239\r
2240 try {\r
2241 s._a.currentTime = position1K;\r
2242 if (s.playState === 0 || s.paused) {\r
2243 // allow seek without auto-play/resume\r
2244 s._a.pause();\r
2245 }\r
2246 } catch(e) {\r
2247 sm2._wD(s.id + ': setPosition(' + position1K + ') failed: ' + e.message, 2);\r
2248 }\r
2249\r
2250 }\r
2251\r
2252 } else if (position1K) {\r
2253\r
2254 // warn on non-zero seek attempts\r
2255 sm2._wD(s.id + ': setPosition(' + position1K + '): Cannot seek yet, sound not ready', 2);\r
2256 return s;\r
2257\r
2258 }\r
2259\r
2260 if (s.paused) {\r
2261\r
2262 // if paused, refresh UI right away\r
2263 // force update\r
2264 s._onTimer(true);\r
2265\r
2266 }\r
2267\r
2268 }\r
2269\r
2270 return s;\r
2271\r
2272 };\r
2273\r
2274 /**\r
2275 * Pauses sound playback.\r
2276 *\r
2277 * @return {SMSound} The SMSound object\r
2278 */\r
2279\r
2280 this.pause = function(_bCallFlash) {\r
2281\r
2282 if (s.paused || (s.playState === 0 && s.readyState !== 1)) {\r
2283 return s;\r
2284 }\r
2285\r
2286 sm2._wD(s.id + ': pause()');\r
2287 s.paused = true;\r
2288\r
2289 if (!s.isHTML5) {\r
2290 if (_bCallFlash || _bCallFlash === _undefined) {\r
2291 flash._pause(s.id, s._iO.multiShot);\r
2292 }\r
2293 } else {\r
2294 s._setup_html5().pause();\r
2295 stop_html5_timer();\r
2296 }\r
2297\r
2298 if (s._iO.onpause) {\r
2299 s._iO.onpause.apply(s);\r
2300 }\r
2301\r
2302 return s;\r
2303\r
2304 };\r
2305\r
2306 /**\r
2307 * Resumes sound playback.\r
2308 *\r
2309 * @return {SMSound} The SMSound object\r
2310 */\r
2311\r
2312 /**\r
2313 * When auto-loaded streams pause on buffer full they have a playState of 0.\r
2314 * We need to make sure that the playState is set to 1 when these streams "resume".\r
2315 * When a paused stream is resumed, we need to trigger the onplay() callback if it\r
2316 * hasn't been called already. In this case since the sound is being played for the\r
2317 * first time, I think it's more appropriate to call onplay() rather than onresume().\r
2318 */\r
2319\r
2320 this.resume = function() {\r
2321\r
2322 var instanceOptions = s._iO;\r
2323\r
2324 if (!s.paused) {\r
2325 return s;\r
2326 }\r
2327\r
2328 sm2._wD(s.id + ': resume()');\r
2329 s.paused = false;\r
2330 s.playState = 1;\r
2331\r
2332 if (!s.isHTML5) {\r
2333 if (instanceOptions.isMovieStar && !instanceOptions.serverURL) {\r
2334 // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.\r
2335 s.setPosition(s.position);\r
2336 }\r
2337 // flash method is toggle-based (pause/resume)\r
2338 flash._pause(s.id, instanceOptions.multiShot);\r
2339 } else {\r
2340 s._setup_html5().play();\r
2341 start_html5_timer();\r
2342 }\r
2343\r
2344 if (!onplay_called && instanceOptions.onplay) {\r
2345 instanceOptions.onplay.apply(s);\r
2346 onplay_called = true;\r
2347 } else if (instanceOptions.onresume) {\r
2348 instanceOptions.onresume.apply(s);\r
2349 }\r
2350\r
2351 return s;\r
2352\r
2353 };\r
2354\r
2355 /**\r
2356 * Toggles sound playback.\r
2357 *\r
2358 * @return {SMSound} The SMSound object\r
2359 */\r
2360\r
2361 this.togglePause = function() {\r
2362\r
2363 sm2._wD(s.id + ': togglePause()');\r
2364\r
2365 if (s.playState === 0) {\r
2366 s.play({\r
2367 position: (fV === 9 && !s.isHTML5 ? s.position : s.position / msecScale)\r
2368 });\r
2369 return s;\r
2370 }\r
2371\r
2372 if (s.paused) {\r
2373 s.resume();\r
2374 } else {\r
2375 s.pause();\r
2376 }\r
2377\r
2378 return s;\r
2379\r
2380 };\r
2381\r
2382 /**\r
2383 * Sets the panning (L-R) effect.\r
2384 *\r
2385 * @param {number} nPan The pan value (-100 to 100)\r
2386 * @return {SMSound} The SMSound object\r
2387 */\r
2388\r
2389 this.setPan = function(nPan, bInstanceOnly) {\r
2390\r
2391 if (nPan === _undefined) {\r
2392 nPan = 0;\r
2393 }\r
2394\r
2395 if (bInstanceOnly === _undefined) {\r
2396 bInstanceOnly = false;\r
2397 }\r
2398\r
2399 if (!s.isHTML5) {\r
2400 flash._setPan(s.id, nPan);\r
2401 } // else { no HTML5 pan? }\r
2402\r
2403 s._iO.pan = nPan;\r
2404\r
2405 if (!bInstanceOnly) {\r
2406 s.pan = nPan;\r
2407 s.options.pan = nPan;\r
2408 }\r
2409\r
2410 return s;\r
2411\r
2412 };\r
2413\r
2414 /**\r
2415 * Sets the volume.\r
2416 *\r
2417 * @param {number} nVol The volume value (0 to 100)\r
2418 * @return {SMSound} The SMSound object\r
2419 */\r
2420\r
2421 this.setVolume = function(nVol, _bInstanceOnly) {\r
2422\r
2423 /**\r
2424 * Note: Setting volume has no effect on iOS "special snowflake" devices.\r
2425 * Hardware volume control overrides software, and volume\r
2426 * will always return 1 per Apple docs. (iOS 4 + 5.)\r
2427 * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html\r
2428 */\r
2429\r
2430 if (nVol === _undefined) {\r
2431 nVol = 100;\r
2432 }\r
2433\r
2434 if (_bInstanceOnly === _undefined) {\r
2435 _bInstanceOnly = false;\r
2436 }\r
2437\r
2438 if (!s.isHTML5) {\r
2439 flash._setVolume(s.id, (sm2.muted && !s.muted) || s.muted?0:nVol);\r
2440 } else if (s._a) {\r
2441 if (sm2.muted && !s.muted) {\r
2442 s.muted = true;\r
2443 s._a.muted = true;\r
2444 }\r
2445 // valid range: 0-1\r
2446 s._a.volume = Math.max(0, Math.min(1, nVol/100));\r
2447 }\r
2448\r
2449 s._iO.volume = nVol;\r
2450\r
2451 if (!_bInstanceOnly) {\r
2452 s.volume = nVol;\r
2453 s.options.volume = nVol;\r
2454 }\r
2455\r
2456 return s;\r
2457\r
2458 };\r
2459\r
2460 /**\r
2461 * Mutes the sound.\r
2462 *\r
2463 * @return {SMSound} The SMSound object\r
2464 */\r
2465\r
2466 this.mute = function() {\r
2467\r
2468 s.muted = true;\r
2469\r
2470 if (!s.isHTML5) {\r
2471 flash._setVolume(s.id, 0);\r
2472 } else if (s._a) {\r
2473 s._a.muted = true;\r
2474 }\r
2475\r
2476 return s;\r
2477\r
2478 };\r
2479\r
2480 /**\r
2481 * Unmutes the sound.\r
2482 *\r
2483 * @return {SMSound} The SMSound object\r
2484 */\r
2485\r
2486 this.unmute = function() {\r
2487\r
2488 s.muted = false;\r
2489 var hasIO = (s._iO.volume !== _undefined);\r
2490\r
2491 if (!s.isHTML5) {\r
2492 flash._setVolume(s.id, hasIO?s._iO.volume:s.options.volume);\r
2493 } else if (s._a) {\r
2494 s._a.muted = false;\r
2495 }\r
2496\r
2497 return s;\r
2498\r
2499 };\r
2500\r
2501 /**\r
2502 * Toggles the muted state of a sound.\r
2503 *\r
2504 * @return {SMSound} The SMSound object\r
2505 */\r
2506\r
2507 this.toggleMute = function() {\r
2508\r
2509 return (s.muted?s.unmute():s.mute());\r
2510\r
2511 };\r
2512\r
2513 /**\r
2514 * Registers a callback to be fired when a sound reaches a given position during playback.\r
2515 *\r
2516 * @param {number} nPosition The position to watch for\r
2517 * @param {function} oMethod The relevant callback to fire\r
2518 * @param {object} oScope Optional: The scope to apply the callback to\r
2519 * @return {SMSound} The SMSound object\r
2520 */\r
2521\r
2522 this.onPosition = function(nPosition, oMethod, oScope) {\r
2523\r
2524 // TODO: basic dupe checking?\r
2525\r
2526 onPositionItems.push({\r
2527 position: parseInt(nPosition, 10),\r
2528 method: oMethod,\r
2529 scope: (oScope !== _undefined ? oScope : s),\r
2530 fired: false\r
2531 });\r
2532\r
2533 return s;\r
2534\r
2535 };\r
2536\r
2537 // legacy/backwards-compability: lower-case method name\r
2538 this.onposition = this.onPosition;\r
2539\r
2540 /**\r
2541 * Removes registered callback(s) from a sound, by position and/or callback.\r
2542 *\r
2543 * @param {number} nPosition The position to clear callback(s) for\r
2544 * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position\r
2545 * @return {SMSound} The SMSound object\r
2546 */\r
2547\r
2548 this.clearOnPosition = function(nPosition, oMethod) {\r
2549\r
2550 var i;\r
2551\r
2552 nPosition = parseInt(nPosition, 10);\r
2553\r
2554 if (isNaN(nPosition)) {\r
2555 // safety check\r
2556 return false;\r
2557 }\r
2558\r
2559 for (i=0; i < onPositionItems.length; i++) {\r
2560\r
2561 if (nPosition === onPositionItems[i].position) {\r
2562 // remove this item if no method was specified, or, if the method matches\r
2563 if (!oMethod || (oMethod === onPositionItems[i].method)) {\r
2564 if (onPositionItems[i].fired) {\r
2565 // decrement "fired" counter, too\r
2566 onPositionFired--;\r
2567 }\r
2568 onPositionItems.splice(i, 1);\r
2569 }\r
2570 }\r
2571\r
2572 }\r
2573\r
2574 };\r
2575\r
2576 this._processOnPosition = function() {\r
2577\r
2578 var i, item, j = onPositionItems.length;\r
2579 \r
2580 if (!j || !s.playState || onPositionFired >= j) {\r
2581 return false;\r
2582 }\r
2583\r
2584 for (i=j-1; i >= 0; i--) {\r
2585 item = onPositionItems[i];\r
2586 if (!item.fired && s.position >= item.position) {\r
2587 item.fired = true;\r
2588 onPositionFired++;\r
2589 item.method.apply(item.scope, [item.position]);\r
2590 j = onPositionItems.length; // reset j -- onPositionItems.length can be changed in the item callback above... occasionally breaking the loop.\r
2591 }\r
2592 }\r
2593 \r
2594 return true;\r
2595\r
2596 };\r
2597\r
2598 this._resetOnPosition = function(nPosition) {\r
2599\r
2600 // reset "fired" for items interested in this position\r
2601 var i, item, j = onPositionItems.length;\r
2602\r
2603 if (!j) {\r
2604 return false;\r
2605 }\r
2606\r
2607 for (i=j-1; i >= 0; i--) {\r
2608 item = onPositionItems[i];\r
2609 if (item.fired && nPosition <= item.position) {\r
2610 item.fired = false;\r
2611 onPositionFired--;\r
2612 }\r
2613 }\r
2614\r
2615 return true;\r
2616\r
2617 };\r
2618\r
2619 /**\r
2620 * SMSound() private internals\r
2621 * --------------------------------\r
2622 */\r
2623\r
2624 applyFromTo = function() {\r
2625\r
2626 var instanceOptions = s._iO,\r
2627 f = instanceOptions.from,\r
2628 t = instanceOptions.to,\r
2629 start, end;\r
2630\r
2631 end = function() {\r
2632\r
2633 // end has been reached.\r
2634 sm2._wD(s.id + ': "To" time of ' + t + ' reached.');\r
2635\r
2636 // detach listener\r
2637 s.clearOnPosition(t, end);\r
2638\r
2639 // stop should clear this, too\r
2640 s.stop();\r
2641\r
2642 };\r
2643\r
2644 start = function() {\r
2645\r
2646 sm2._wD(s.id + ': Playing "from" ' + f);\r
2647\r
2648 // add listener for end\r
2649 if (t !== null && !isNaN(t)) {\r
2650 s.onPosition(t, end);\r
2651 }\r
2652\r
2653 };\r
2654\r
2655 if (f !== null && !isNaN(f)) {\r
2656\r
2657 // apply to instance options, guaranteeing correct start position.\r
2658 instanceOptions.position = f;\r
2659\r
2660 // multiShot timing can't be tracked, so prevent that.\r
2661 instanceOptions.multiShot = false;\r
2662\r
2663 start();\r
2664\r
2665 }\r
2666\r
2667 // return updated instanceOptions including starting position\r
2668 return instanceOptions;\r
2669\r
2670 };\r
2671\r
2672 attachOnPosition = function() {\r
2673\r
2674 var item,\r
2675 op = s._iO.onposition;\r
2676\r
2677 // attach onposition things, if any, now.\r
2678\r
2679 if (op) {\r
2680\r
2681 for (item in op) {\r
2682 if (op.hasOwnProperty(item)) {\r
2683 s.onPosition(parseInt(item, 10), op[item]);\r
2684 }\r
2685 }\r
2686\r
2687 }\r
2688\r
2689 };\r
2690\r
2691 detachOnPosition = function() {\r
2692\r
2693 var item,\r
2694 op = s._iO.onposition;\r
2695\r
2696 // detach any onposition()-style listeners.\r
2697\r
2698 if (op) {\r
2699\r
2700 for (item in op) {\r
2701 if (op.hasOwnProperty(item)) {\r
2702 s.clearOnPosition(parseInt(item, 10));\r
2703 }\r
2704 }\r
2705\r
2706 }\r
2707\r
2708 };\r
2709\r
2710 start_html5_timer = function() {\r
2711\r
2712 if (s.isHTML5) {\r
2713 startTimer(s);\r
2714 }\r
2715\r
2716 };\r
2717\r
2718 stop_html5_timer = function() {\r
2719\r
2720 if (s.isHTML5) {\r
2721 stopTimer(s);\r
2722 }\r
2723\r
2724 };\r
2725\r
2726 resetProperties = function(retainPosition) {\r
2727\r
2728 if (!retainPosition) {\r
2729 onPositionItems = [];\r
2730 onPositionFired = 0;\r
2731 }\r
2732\r
2733 onplay_called = false;\r
2734\r
2735 s._hasTimer = null;\r
2736 s._a = null;\r
2737 s._html5_canplay = false;\r
2738 s.bytesLoaded = null;\r
2739 s.bytesTotal = null;\r
2740 s.duration = (s._iO && s._iO.duration ? s._iO.duration : null);\r
2741 s.durationEstimate = null;\r
2742 s.buffered = [];\r
2743\r
2744 // legacy: 1D array\r
2745 s.eqData = [];\r
2746\r
2747 s.eqData.left = [];\r
2748 s.eqData.right = [];\r
2749\r
2750 s.failures = 0;\r
2751 s.isBuffering = false;\r
2752 s.instanceOptions = {};\r
2753 s.instanceCount = 0;\r
2754 s.loaded = false;\r
2755 s.metadata = {};\r
2756\r
2757 // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success\r
2758 s.readyState = 0;\r
2759\r
2760 s.muted = false;\r
2761 s.paused = false;\r
2762\r
2763 s.peakData = {\r
2764 left: 0,\r
2765 right: 0\r
2766 };\r
2767\r
2768 s.waveformData = {\r
2769 left: [],\r
2770 right: []\r
2771 };\r
2772\r
2773 s.playState = 0;\r
2774 s.position = null;\r
2775\r
2776 s.id3 = {};\r
2777\r
2778 };\r
2779\r
2780 resetProperties();\r
2781\r
2782 /**\r
2783 * Pseudo-private SMSound internals\r
2784 * --------------------------------\r
2785 */\r
2786\r
2787 this._onTimer = function(bForce) {\r
2788\r
2789 /**\r
2790 * HTML5-only _whileplaying() etc.\r
2791 * called from both HTML5 native events, and polling/interval-based timers\r
2792 * mimics flash and fires only when time/duration change, so as to be polling-friendly\r
2793 */\r
2794\r
2795 var duration, isNew = false, time, x = {};\r
2796\r
2797 if (s._hasTimer || bForce) {\r
2798\r
2799 // TODO: May not need to track readyState (1 = loading)\r
2800\r
2801 if (s._a && (bForce || ((s.playState > 0 || s.readyState === 1) && !s.paused))) {\r
2802\r
2803 duration = s._get_html5_duration();\r
2804\r
2805 if (duration !== lastHTML5State.duration) {\r
2806\r
2807 lastHTML5State.duration = duration;\r
2808 s.duration = duration;\r
2809 isNew = true;\r
2810\r
2811 }\r
2812\r
2813 // TODO: investigate why this goes wack if not set/re-set each time.\r
2814 s.durationEstimate = s.duration;\r
2815\r
2816 time = (s._a.currentTime * msecScale || 0);\r
2817\r
2818 if (time !== lastHTML5State.time) {\r
2819\r
2820 lastHTML5State.time = time;\r
2821 isNew = true;\r
2822\r
2823 }\r
2824\r
2825 if (isNew || bForce) {\r
2826\r
2827 s._whileplaying(time,x,x,x,x);\r
2828\r
2829 }\r
2830\r
2831 }/* else {\r
2832\r
2833 // sm2._wD('_onTimer: Warn for "'+s.id+'": '+(!s._a?'Could not find element. ':'')+(s.playState === 0?'playState bad, 0?':'playState = '+s.playState+', OK'));\r
2834\r
2835 return false;\r
2836\r
2837 }*/\r
2838\r
2839 return isNew;\r
2840\r
2841 }\r
2842\r
2843 };\r
2844\r
2845 this._get_html5_duration = function() {\r
2846\r
2847 var instanceOptions = s._iO,\r
2848 // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null\r
2849 d = (s._a && s._a.duration ? s._a.duration*msecScale : (instanceOptions && instanceOptions.duration ? instanceOptions.duration : null)),\r
2850 result = (d && !isNaN(d) && d !== Infinity ? d : null);\r
2851\r
2852 return result;\r
2853\r
2854 };\r
2855\r
2856 this._apply_loop = function(a, nLoops) {\r
2857\r
2858 /**\r
2859 * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop\r
2860 * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.\r
2861 */\r
2862\r
2863 // <d>\r
2864 if (!a.loop && nLoops > 1) {\r
2865 sm2._wD('Note: Native HTML5 looping is infinite.', 1);\r
2866 }\r
2867 // </d>\r
2868\r
2869 a.loop = (nLoops > 1 ? 'loop' : '');\r
2870\r
2871 };\r
2872\r
2873 this._setup_html5 = function(oOptions) {\r
2874\r
2875 var instanceOptions = mixin(s._iO, oOptions),\r
2876 a = useGlobalHTML5Audio ? globalHTML5Audio : s._a,\r
2877 dURL = decodeURI(instanceOptions.url),\r
2878 sameURL;\r
2879\r
2880 /**\r
2881 * "First things first, I, Poppa..." (reset the previous state of the old sound, if playing)\r
2882 * Fixes case with devices that can only play one sound at a time\r
2883 * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state\r
2884 */\r
2885\r
2886 if (useGlobalHTML5Audio) {\r
2887\r
2888 if (dURL === decodeURI(lastGlobalHTML5URL)) {\r
2889 // global HTML5 audio: re-use of URL\r
2890 sameURL = true;\r
2891 }\r
2892\r
2893 } else if (dURL === decodeURI(lastURL)) {\r
2894\r
2895 // options URL is the same as the "last" URL, and we used (loaded) it\r
2896 sameURL = true;\r
2897\r
2898 }\r
2899\r
2900 if (a) {\r
2901\r
2902 if (a._s) {\r
2903\r
2904 if (useGlobalHTML5Audio) {\r
2905\r
2906 if (a._s && a._s.playState && !sameURL) {\r
2907\r
2908 // global HTML5 audio case, and loading a new URL. stop the currently-playing one.\r
2909 a._s.stop();\r
2910\r
2911 }\r
2912\r
2913 } else if (!useGlobalHTML5Audio && dURL === decodeURI(lastURL)) {\r
2914\r
2915 // non-global HTML5 reuse case: same url, ignore request\r
2916 s._apply_loop(a, instanceOptions.loops);\r
2917\r
2918 return a;\r
2919\r
2920 }\r
2921\r
2922 }\r
2923\r
2924 if (!sameURL) {\r
2925\r
2926 // don't retain onPosition() stuff with new URLs.\r
2927\r
2928 if (lastURL) {\r
2929 resetProperties(false);\r
2930 }\r
2931\r
2932 // assign new HTML5 URL\r
2933\r
2934 a.src = instanceOptions.url;\r
2935\r
2936 s.url = instanceOptions.url;\r
2937\r
2938 lastURL = instanceOptions.url;\r
2939\r
2940 lastGlobalHTML5URL = instanceOptions.url;\r
2941\r
2942 a._called_load = false;\r
2943\r
2944 }\r
2945\r
2946 } else {\r
2947\r
2948 if (instanceOptions.autoLoad || instanceOptions.autoPlay) {\r
2949\r
2950 s._a = new Audio(instanceOptions.url);\r
2951 s._a.load();\r
2952\r
2953 } else {\r
2954\r
2955 // null for stupid Opera 9.64 case\r
2956 s._a = (isOpera && opera.version() < 10 ? new Audio(null) : new Audio());\r
2957\r
2958 }\r
2959\r
2960 // assign local reference\r
2961 a = s._a;\r
2962\r
2963 a._called_load = false;\r
2964\r
2965 if (useGlobalHTML5Audio) {\r
2966\r
2967 globalHTML5Audio = a;\r
2968\r
2969 }\r
2970\r
2971 }\r
2972\r
2973 s.isHTML5 = true;\r
2974\r
2975 // store a ref on the track\r
2976 s._a = a;\r
2977\r
2978 // store a ref on the audio\r
2979 a._s = s;\r
2980\r
2981 add_html5_events();\r
2982\r
2983 s._apply_loop(a, instanceOptions.loops);\r
2984\r
2985 if (instanceOptions.autoLoad || instanceOptions.autoPlay) {\r
2986\r
2987 s.load();\r
2988\r
2989 } else {\r
2990\r
2991 // early HTML5 implementation (non-standard)\r
2992 a.autobuffer = false;\r
2993\r
2994 // standard ('none' is also an option.)\r
2995 a.preload = 'auto';\r
2996\r
2997 }\r
2998\r
2999 return a;\r
3000\r
3001 };\r
3002\r
3003 add_html5_events = function() {\r
3004\r
3005 if (s._a._added_events) {\r
3006 return false;\r
3007 }\r
3008\r
3009 var f;\r
3010\r
3011 function add(oEvt, oFn, bCapture) {\r
3012 return s._a ? s._a.addEventListener(oEvt, oFn, bCapture||false) : null;\r
3013 }\r
3014\r
3015 s._a._added_events = true;\r
3016\r
3017 for (f in html5_events) {\r
3018 if (html5_events.hasOwnProperty(f)) {\r
3019 add(f, html5_events[f]);\r
3020 }\r
3021 }\r
3022\r
3023 return true;\r
3024\r
3025 };\r
3026\r
3027 remove_html5_events = function() {\r
3028\r
3029 // Remove event listeners\r
3030\r
3031 var f;\r
3032\r
3033 function remove(oEvt, oFn, bCapture) {\r
3034 return (s._a ? s._a.removeEventListener(oEvt, oFn, bCapture||false) : null);\r
3035 }\r
3036\r
3037 sm2._wD(s.id + ': Removing event listeners');\r
3038 s._a._added_events = false;\r
3039\r
3040 for (f in html5_events) {\r
3041 if (html5_events.hasOwnProperty(f)) {\r
3042 remove(f, html5_events[f]);\r
3043 }\r
3044 }\r
3045\r
3046 };\r
3047\r
3048 /**\r
3049 * Pseudo-private event internals\r
3050 * ------------------------------\r
3051 */\r
3052\r
3053 this._onload = function(nSuccess) {\r
3054\r
3055 var fN,\r
3056 // check for duration to prevent false positives from flash 8 when loading from cache.\r
3057 loadOK = !!nSuccess || (!s.isHTML5 && fV === 8 && s.duration);\r
3058\r
3059 // <d>\r
3060 fN = s.id + ': ';\r
3061 sm2._wD(fN + (loadOK ? 'onload()' : 'Failed to load / invalid sound?' + (!s.duration ? ' Zero-length duration reported.' : ' -') + ' (' + s.url + ')'), (loadOK ? 1 : 2));\r
3062 if (!loadOK && !s.isHTML5) {\r
3063 if (sm2.sandbox.noRemote === true) {\r
3064 sm2._wD(fN + str('noNet'), 1);\r
3065 }\r
3066 if (sm2.sandbox.noLocal === true) {\r
3067 sm2._wD(fN + str('noLocal'), 1);\r
3068 }\r
3069 }\r
3070 // </d>\r
3071\r
3072 s.loaded = loadOK;\r
3073 s.readyState = loadOK?3:2;\r
3074 s._onbufferchange(0);\r
3075\r
3076 if (s._iO.onload) {\r
3077 wrapCallback(s, function() {\r
3078 s._iO.onload.apply(s, [loadOK]);\r
3079 });\r
3080 }\r
3081\r
3082 return true;\r
3083\r
3084 };\r
3085\r
3086 this._onbufferchange = function(nIsBuffering) {\r
3087\r
3088 if (s.playState === 0) {\r
3089 // ignore if not playing\r
3090 return false;\r
3091 }\r
3092\r
3093 if ((nIsBuffering && s.isBuffering) || (!nIsBuffering && !s.isBuffering)) {\r
3094 return false;\r
3095 }\r
3096\r
3097 s.isBuffering = (nIsBuffering === 1);\r
3098 if (s._iO.onbufferchange) {\r
3099 sm2._wD(s.id + ': Buffer state change: ' + nIsBuffering);\r
3100 s._iO.onbufferchange.apply(s);\r
3101 }\r
3102\r
3103 return true;\r
3104\r
3105 };\r
3106\r
3107 /**\r
3108 * Playback may have stopped due to buffering, or related reason.\r
3109 * This state can be encountered on iOS < 6 when auto-play is blocked.\r
3110 */\r
3111\r
3112 this._onsuspend = function() {\r
3113\r
3114 if (s._iO.onsuspend) {\r
3115 sm2._wD(s.id + ': Playback suspended');\r
3116 s._iO.onsuspend.apply(s);\r
3117 }\r
3118\r
3119 return true;\r
3120\r
3121 };\r
3122\r
3123 /**\r
3124 * flash 9/movieStar + RTMP-only method, should fire only once at most\r
3125 * at this point we just recreate failed sounds rather than trying to reconnect\r
3126 */\r
3127\r
3128 this._onfailure = function(msg, level, code) {\r
3129\r
3130 s.failures++;\r
3131 sm2._wD(s.id + ': Failures = ' + s.failures);\r
3132\r
3133 if (s._iO.onfailure && s.failures === 1) {\r
3134 s._iO.onfailure(s, msg, level, code);\r
3135 } else {\r
3136 sm2._wD(s.id + ': Ignoring failure');\r
3137 }\r
3138\r
3139 };\r
3140\r
3141 this._onfinish = function() {\r
3142\r
3143 // store local copy before it gets trashed...\r
3144 var io_onfinish = s._iO.onfinish;\r
3145\r
3146 s._onbufferchange(0);\r
3147 s._resetOnPosition(0);\r
3148\r
3149 // reset some state items\r
3150 if (s.instanceCount) {\r
3151\r
3152 s.instanceCount--;\r
3153\r
3154 if (!s.instanceCount) {\r
3155\r
3156 // remove onPosition listeners, if any\r
3157 detachOnPosition();\r
3158\r
3159 // reset instance options\r
3160 s.playState = 0;\r
3161 s.paused = false;\r
3162 s.instanceCount = 0;\r
3163 s.instanceOptions = {};\r
3164 s._iO = {};\r
3165 stop_html5_timer();\r
3166\r
3167 // reset position, too\r
3168 if (s.isHTML5) {\r
3169 s.position = 0;\r
3170 }\r
3171\r
3172 }\r
3173\r
3174 if (!s.instanceCount || s._iO.multiShotEvents) {\r
3175 // fire onfinish for last, or every instance\r
3176 if (io_onfinish) {\r
3177 sm2._wD(s.id + ': onfinish()');\r
3178 wrapCallback(s, function() {\r
3179 io_onfinish.apply(s);\r
3180 });\r
3181 }\r
3182 }\r
3183\r
3184 }\r
3185\r
3186 };\r
3187\r
3188 this._whileloading = function(nBytesLoaded, nBytesTotal, nDuration, nBufferLength) {\r
3189\r
3190 var instanceOptions = s._iO;\r
3191\r
3192 s.bytesLoaded = nBytesLoaded;\r
3193 s.bytesTotal = nBytesTotal;\r
3194 s.duration = Math.floor(nDuration);\r
3195 s.bufferLength = nBufferLength;\r
3196\r
3197 if (!s.isHTML5 && !instanceOptions.isMovieStar) {\r
3198\r
3199 if (instanceOptions.duration) {\r
3200 // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.\r
3201 s.durationEstimate = (s.duration > instanceOptions.duration) ? s.duration : instanceOptions.duration;\r
3202 } else {\r
3203 s.durationEstimate = parseInt((s.bytesTotal / s.bytesLoaded) * s.duration, 10);\r
3204 }\r
3205\r
3206 } else {\r
3207\r
3208 s.durationEstimate = s.duration;\r
3209\r
3210 }\r
3211\r
3212 // for flash, reflect sequential-load-style buffering\r
3213 if (!s.isHTML5) {\r
3214 s.buffered = [{\r
3215 'start': 0,\r
3216 'end': s.duration\r
3217 }];\r
3218 }\r
3219\r
3220 // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials\r
3221 if ((s.readyState !== 3 || s.isHTML5) && instanceOptions.whileloading) {\r
3222 instanceOptions.whileloading.apply(s);\r
3223 }\r
3224\r
3225 };\r
3226\r
3227 this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {\r
3228\r
3229 var instanceOptions = s._iO,\r
3230 eqLeft;\r
3231\r
3232 if (isNaN(nPosition) || nPosition === null) {\r
3233 // flash safety net\r
3234 return false;\r
3235 }\r
3236\r
3237 // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.\r
3238 s.position = Math.max(0, nPosition);\r
3239\r
3240 s._processOnPosition();\r
3241\r
3242 if (!s.isHTML5 && fV > 8) {\r
3243\r
3244 if (instanceOptions.usePeakData && oPeakData !== _undefined && oPeakData) {\r
3245 s.peakData = {\r
3246 left: oPeakData.leftPeak,\r
3247 right: oPeakData.rightPeak\r
3248 };\r
3249 }\r
3250\r
3251 if (instanceOptions.useWaveformData && oWaveformDataLeft !== _undefined && oWaveformDataLeft) {\r
3252 s.waveformData = {\r
3253 left: oWaveformDataLeft.split(','),\r
3254 right: oWaveformDataRight.split(',')\r
3255 };\r
3256 }\r
3257\r
3258 if (instanceOptions.useEQData) {\r
3259 if (oEQData !== _undefined && oEQData && oEQData.leftEQ) {\r
3260 eqLeft = oEQData.leftEQ.split(',');\r
3261 s.eqData = eqLeft;\r
3262 s.eqData.left = eqLeft;\r
3263 if (oEQData.rightEQ !== _undefined && oEQData.rightEQ) {\r
3264 s.eqData.right = oEQData.rightEQ.split(',');\r
3265 }\r
3266 }\r
3267 }\r
3268\r
3269 }\r
3270\r
3271 if (s.playState === 1) {\r
3272\r
3273 // special case/hack: ensure buffering is false if loading from cache (and not yet started)\r
3274 if (!s.isHTML5 && fV === 8 && !s.position && s.isBuffering) {\r
3275 s._onbufferchange(0);\r
3276 }\r
3277\r
3278 if (instanceOptions.whileplaying) {\r
3279 // flash may call after actual finish\r
3280 instanceOptions.whileplaying.apply(s);\r
3281 }\r
3282\r
3283 }\r
3284\r
3285 return true;\r
3286\r
3287 };\r
3288\r
3289 this._oncaptiondata = function(oData) {\r
3290\r
3291 /**\r
3292 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature\r
3293 *\r
3294 * @param {object} oData\r
3295 */\r
3296\r
3297 sm2._wD(s.id + ': Caption data received.');\r
3298\r
3299 s.captiondata = oData;\r
3300\r
3301 if (s._iO.oncaptiondata) {\r
3302 s._iO.oncaptiondata.apply(s, [oData]);\r
3303 }\r
3304\r
3305 };\r
3306\r
3307 this._onmetadata = function(oMDProps, oMDData) {\r
3308\r
3309 /**\r
3310 * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature\r
3311 * RTMP may include song title, MovieStar content may include encoding info\r
3312 *\r
3313 * @param {array} oMDProps (names)\r
3314 * @param {array} oMDData (values)\r
3315 */\r
3316\r
3317 sm2._wD(s.id + ': Metadata received.');\r
3318\r
3319 var oData = {}, i, j;\r
3320\r
3321 for (i = 0, j = oMDProps.length; i < j; i++) {\r
3322 oData[oMDProps[i]] = oMDData[i];\r
3323 }\r
3324 s.metadata = oData;\r
3325\r
3326 if (s._iO.onmetadata) {\r
3327 s._iO.onmetadata.apply(s);\r
3328 }\r
3329\r
3330 };\r
3331\r
3332 this._onid3 = function(oID3Props, oID3Data) {\r
3333\r
3334 /**\r
3335 * internal: flash 8 + flash 9 ID3 feature\r
3336 * may include artist, song title etc.\r
3337 *\r
3338 * @param {array} oID3Props (names)\r
3339 * @param {array} oID3Data (values)\r
3340 */\r
3341\r
3342 sm2._wD(s.id + ': ID3 data received.');\r
3343\r
3344 var oData = [], i, j;\r
3345\r
3346 for (i = 0, j = oID3Props.length; i < j; i++) {\r
3347 oData[oID3Props[i]] = oID3Data[i];\r
3348 }\r
3349 s.id3 = mixin(s.id3, oData);\r
3350\r
3351 if (s._iO.onid3) {\r
3352 s._iO.onid3.apply(s);\r
3353 }\r
3354\r
3355 };\r
3356\r
3357 // flash/RTMP-only\r
3358\r
3359 this._onconnect = function(bSuccess) {\r
3360\r
3361 bSuccess = (bSuccess === 1);\r
3362 sm2._wD(s.id + ': ' + (bSuccess ? 'Connected.' : 'Failed to connect? - ' + s.url), (bSuccess ? 1 : 2));\r
3363 s.connected = bSuccess;\r
3364\r
3365 if (bSuccess) {\r
3366\r
3367 s.failures = 0;\r
3368\r
3369 if (idCheck(s.id)) {\r
3370 if (s.getAutoPlay()) {\r
3371 // only update the play state if auto playing\r
3372 s.play(_undefined, s.getAutoPlay());\r
3373 } else if (s._iO.autoLoad) {\r
3374 s.load();\r
3375 }\r
3376 }\r
3377\r
3378 if (s._iO.onconnect) {\r
3379 s._iO.onconnect.apply(s, [bSuccess]);\r
3380 }\r
3381\r
3382 }\r
3383\r
3384 };\r
3385\r
3386 this._ondataerror = function(sError) {\r
3387\r
3388 // flash 9 wave/eq data handler\r
3389 // hack: called at start, and end from flash at/after onfinish()\r
3390 if (s.playState > 0) {\r
3391 sm2._wD(s.id + ': Data error: ' + sError);\r
3392 if (s._iO.ondataerror) {\r
3393 s._iO.ondataerror.apply(s);\r
3394 }\r
3395 }\r
3396\r
3397 };\r
3398\r
3399 // <d>\r
3400 this._debug();\r
3401 // </d>\r
3402\r
3403 }; // SMSound()\r
3404\r
3405 /**\r
3406 * Private SoundManager internals\r
3407 * ------------------------------\r
3408 */\r
3409\r
3410 getDocument = function() {\r
3411\r
3412 return (doc.body || doc.getElementsByTagName('div')[0]);\r
3413\r
3414 };\r
3415\r
3416 id = function(sID) {\r
3417\r
3418 return doc.getElementById(sID);\r
3419\r
3420 };\r
3421\r
3422 mixin = function(oMain, oAdd) {\r
3423\r
3424 // non-destructive merge\r
3425 var o1 = (oMain || {}), o2, o;\r
3426\r
3427 // if unspecified, o2 is the default options object\r
3428 o2 = (oAdd === _undefined ? sm2.defaultOptions : oAdd);\r
3429\r
3430 for (o in o2) {\r
3431\r
3432 if (o2.hasOwnProperty(o) && o1[o] === _undefined) {\r
3433\r
3434 if (typeof o2[o] !== 'object' || o2[o] === null) {\r
3435\r
3436 // assign directly\r
3437 o1[o] = o2[o];\r
3438\r
3439 } else {\r
3440\r
3441 // recurse through o2\r
3442 o1[o] = mixin(o1[o], o2[o]);\r
3443\r
3444 }\r
3445\r
3446 }\r
3447\r
3448 }\r
3449\r
3450 return o1;\r
3451\r
3452 };\r
3453\r
3454 wrapCallback = function(oSound, callback) {\r
3455\r
3456 /**\r
3457 * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue\r
3458 * setTimeout() fix for certain SMSound callbacks like onload() and onfinish(), where subsequent calls like play() and load() fail when Flash Player 11.6.602.171 is installed, and using soundManager with flashVersion = 8 (which is the default).\r
3459 * Not sure of exact cause. Suspect race condition and/or invalid (NaN-style) position argument trickling down to the next JS -> Flash _start() call, in the play() case.\r
3460 * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.\r
3461 * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player\r
3462 */\r
3463 if (!oSound.isHTML5 && fV === 8) {\r
3464 window.setTimeout(callback, 0);\r
3465 } else {\r
3466 callback();\r
3467 }\r
3468\r
3469 };\r
3470\r
3471 // additional soundManager properties that soundManager.setup() will accept\r
3472\r
3473 extraOptions = {\r
3474 'onready': 1,\r
3475 'ontimeout': 1,\r
3476 'defaultOptions': 1,\r
3477 'flash9Options': 1,\r
3478 'movieStarOptions': 1\r
3479 };\r
3480\r
3481 assign = function(o, oParent) {\r
3482\r
3483 /**\r
3484 * recursive assignment of properties, soundManager.setup() helper\r
3485 * allows property assignment based on whitelist\r
3486 */\r
3487\r
3488 var i,\r
3489 result = true,\r
3490 hasParent = (oParent !== _undefined),\r
3491 setupOptions = sm2.setupOptions,\r
3492 bonusOptions = extraOptions;\r
3493\r
3494 // <d>\r
3495\r
3496 // if soundManager.setup() called, show accepted parameters.\r
3497\r
3498 if (o === _undefined) {\r
3499\r
3500 result = [];\r
3501\r
3502 for (i in setupOptions) {\r
3503\r
3504 if (setupOptions.hasOwnProperty(i)) {\r
3505 result.push(i);\r
3506 }\r
3507\r
3508 }\r
3509\r
3510 for (i in bonusOptions) {\r
3511\r
3512 if (bonusOptions.hasOwnProperty(i)) {\r
3513\r
3514 if (typeof sm2[i] === 'object') {\r
3515\r
3516 result.push(i+': {...}');\r
3517\r
3518 } else if (sm2[i] instanceof Function) {\r
3519\r
3520 result.push(i+': function() {...}');\r
3521\r
3522 } else {\r
3523\r
3524 result.push(i);\r
3525\r
3526 }\r
3527\r
3528 }\r
3529\r
3530 }\r
3531\r
3532 sm2._wD(str('setup', result.join(', ')));\r
3533\r
3534 return false;\r
3535\r
3536 }\r
3537\r
3538 // </d>\r
3539\r
3540 for (i in o) {\r
3541\r
3542 if (o.hasOwnProperty(i)) {\r
3543\r
3544 // if not an {object} we want to recurse through...\r
3545\r
3546 if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array || o[i] instanceof RegExp) {\r
3547\r
3548 // check "allowed" options\r
3549\r
3550 if (hasParent && bonusOptions[oParent] !== _undefined) {\r
3551\r
3552 // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }\r
3553 sm2[oParent][i] = o[i];\r
3554\r
3555 } else if (setupOptions[i] !== _undefined) {\r
3556\r
3557 // special case: assign to setupOptions object, which soundManager property references\r
3558 sm2.setupOptions[i] = o[i];\r
3559\r
3560 // assign directly to soundManager, too\r
3561 sm2[i] = o[i];\r
3562\r
3563 } else if (bonusOptions[i] === _undefined) {\r
3564\r
3565 // invalid or disallowed parameter. complain.\r
3566 complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);\r
3567\r
3568 result = false;\r
3569\r
3570 } else {\r
3571\r
3572 /**\r
3573 * valid extraOptions (bonusOptions) parameter.\r
3574 * is it a method, like onready/ontimeout? call it.\r
3575 * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});\r
3576 */\r
3577\r
3578 if (sm2[i] instanceof Function) {\r
3579\r
3580 sm2[i].apply(sm2, (o[i] instanceof Array? o[i] : [o[i]]));\r
3581\r
3582 } else {\r
3583\r
3584 // good old-fashioned direct assignment\r
3585 sm2[i] = o[i];\r
3586\r
3587 }\r
3588\r
3589 }\r
3590\r
3591 } else {\r
3592\r
3593 // recursion case, eg., { defaultOptions: { ... } }\r
3594\r
3595 if (bonusOptions[i] === _undefined) {\r
3596\r
3597 // invalid or disallowed parameter. complain.\r
3598 complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);\r
3599\r
3600 result = false;\r
3601\r
3602 } else {\r
3603\r
3604 // recurse through object\r
3605 return assign(o[i], i);\r
3606\r
3607 }\r
3608\r
3609 }\r
3610\r
3611 }\r
3612\r
3613 }\r
3614\r
3615 return result;\r
3616\r
3617 };\r
3618\r
3619 function preferFlashCheck(kind) {\r
3620\r
3621 // whether flash should play a given type\r
3622 return (sm2.preferFlash && hasFlash && !sm2.ignoreFlash && (sm2.flash[kind] !== _undefined && sm2.flash[kind]));\r
3623\r
3624 }\r
3625\r
3626 /**\r
3627 * Internal DOM2-level event helpers\r
3628 * ---------------------------------\r
3629 */\r
3630\r
3631 event = (function() {\r
3632\r
3633 // normalize event methods\r
3634 var old = (window.attachEvent),\r
3635 evt = {\r
3636 add: (old?'attachEvent':'addEventListener'),\r
3637 remove: (old?'detachEvent':'removeEventListener')\r
3638 };\r
3639\r
3640 // normalize "on" event prefix, optional capture argument\r
3641 function getArgs(oArgs) {\r
3642\r
3643 var args = slice.call(oArgs),\r
3644 len = args.length;\r
3645\r
3646 if (old) {\r
3647 // prefix\r
3648 args[1] = 'on' + args[1];\r
3649 if (len > 3) {\r
3650 // no capture\r
3651 args.pop();\r
3652 }\r
3653 } else if (len === 3) {\r
3654 args.push(false);\r
3655 }\r
3656\r
3657 return args;\r
3658\r
3659 }\r
3660\r
3661 function apply(args, sType) {\r
3662\r
3663 // normalize and call the event method, with the proper arguments\r
3664 var element = args.shift(),\r
3665 method = [evt[sType]];\r
3666\r
3667 if (old) {\r
3668 // old IE can't do apply().\r
3669 element[method](args[0], args[1]);\r
3670 } else {\r
3671 element[method].apply(element, args);\r
3672 }\r
3673\r
3674 }\r
3675\r
3676 function add() {\r
3677\r
3678 apply(getArgs(arguments), 'add');\r
3679\r
3680 }\r
3681\r
3682 function remove() {\r
3683\r
3684 apply(getArgs(arguments), 'remove');\r
3685\r
3686 }\r
3687\r
3688 return {\r
3689 'add': add,\r
3690 'remove': remove\r
3691 };\r
3692\r
3693 }());\r
3694\r
3695 /**\r
3696 * Internal HTML5 event handling\r
3697 * -----------------------------\r
3698 */\r
3699\r
3700 function html5_event(oFn) {\r
3701\r
3702 // wrap html5 event handlers so we don't call them on destroyed and/or unloaded sounds\r
3703\r
3704 return function(e) {\r
3705\r
3706 var s = this._s,\r
3707 result;\r
3708\r
3709 if (!s || !s._a) {\r
3710 // <d>\r
3711 if (s && s.id) {\r
3712 sm2._wD(s.id + ': Ignoring ' + e.type);\r
3713 } else {\r
3714 sm2._wD(h5 + 'Ignoring ' + e.type);\r
3715 }\r
3716 // </d>\r
3717 result = null;\r
3718 } else {\r
3719 result = oFn.call(this, e);\r
3720 }\r
3721\r
3722 return result;\r
3723\r
3724 };\r
3725\r
3726 }\r
3727\r
3728 html5_events = {\r
3729\r
3730 // HTML5 event-name-to-handler map\r
3731\r
3732 abort: html5_event(function() {\r
3733\r
3734 sm2._wD(this._s.id + ': abort');\r
3735\r
3736 }),\r
3737\r
3738 // enough has loaded to play\r
3739\r
3740 canplay: html5_event(function() {\r
3741\r
3742 var s = this._s,\r
3743 position1K;\r
3744\r
3745 if (s._html5_canplay) {\r
3746 // this event has already fired. ignore.\r
3747 return true;\r
3748 }\r
3749\r
3750 s._html5_canplay = true;\r
3751 sm2._wD(s.id + ': canplay');\r
3752 s._onbufferchange(0);\r
3753\r
3754 // position according to instance options\r
3755 position1K = (s._iO.position !== _undefined && !isNaN(s._iO.position)?s._iO.position/msecScale:null);\r
3756\r
3757 // set the position if position was set before the sound loaded\r
3758 if (s.position && this.currentTime !== position1K) {\r
3759 sm2._wD(s.id + ': canplay: Setting position to ' + position1K);\r
3760 try {\r
3761 this.currentTime = position1K;\r
3762 } catch(ee) {\r
3763 sm2._wD(s.id + ': canplay: Setting position of ' + position1K + ' failed: ' + ee.message, 2);\r
3764 }\r
3765 }\r
3766\r
3767 // hack for HTML5 from/to case\r
3768 if (s._iO._oncanplay) {\r
3769 s._iO._oncanplay();\r
3770 }\r
3771\r
3772 }),\r
3773\r
3774 canplaythrough: html5_event(function() {\r
3775\r
3776 var s = this._s;\r
3777\r
3778 if (!s.loaded) {\r
3779 s._onbufferchange(0);\r
3780 s._whileloading(s.bytesLoaded, s.bytesTotal, s._get_html5_duration());\r
3781 s._onload(true);\r
3782 }\r
3783\r
3784 }),\r
3785\r
3786 // TODO: Reserved for potential use\r
3787 /*\r
3788 emptied: html5_event(function() {\r
3789\r
3790 sm2._wD(this._s.id + ': emptied');\r
3791\r
3792 }),\r
3793 */\r
3794\r
3795 ended: html5_event(function() {\r
3796\r
3797 var s = this._s;\r
3798\r
3799 sm2._wD(s.id + ': ended');\r
3800\r
3801 s._onfinish();\r
3802\r
3803 }),\r
3804\r
3805 error: html5_event(function() {\r
3806\r
3807 sm2._wD(this._s.id + ': HTML5 error, code ' + this.error.code);\r
3808 /**\r
3809 * HTML5 error codes, per W3C\r
3810 * Error 1: Client aborted download at user's request.\r
3811 * Error 2: Network error after load started.\r
3812 * Error 3: Decoding issue.\r
3813 * Error 4: Media (audio file) not supported.\r
3814 * Reference: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes\r
3815 */\r
3816 // call load with error state?\r
3817 this._s._onload(false);\r
3818\r
3819 }),\r
3820\r
3821 loadeddata: html5_event(function() {\r
3822\r
3823 var s = this._s;\r
3824\r
3825 sm2._wD(s.id + ': loadeddata');\r
3826\r
3827 // safari seems to nicely report progress events, eventually totalling 100%\r
3828 if (!s._loaded && !isSafari) {\r
3829 s.duration = s._get_html5_duration();\r
3830 }\r
3831\r
3832 }),\r
3833\r
3834 loadedmetadata: html5_event(function() {\r
3835\r
3836 sm2._wD(this._s.id + ': loadedmetadata');\r
3837\r
3838 }),\r
3839\r
3840 loadstart: html5_event(function() {\r
3841\r
3842 sm2._wD(this._s.id + ': loadstart');\r
3843 // assume buffering at first\r
3844 this._s._onbufferchange(1);\r
3845\r
3846 }),\r
3847\r
3848 play: html5_event(function() {\r
3849\r
3850 // sm2._wD(this._s.id + ': play()');\r
3851 // once play starts, no buffering\r
3852 this._s._onbufferchange(0);\r
3853\r
3854 }),\r
3855\r
3856 playing: html5_event(function() {\r
3857\r
3858 sm2._wD(this._s.id + ': playing');\r
3859 // once play starts, no buffering\r
3860 this._s._onbufferchange(0);\r
3861\r
3862 }),\r
3863\r
3864 progress: html5_event(function(e) {\r
3865\r
3866 // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials\r
3867\r
3868 var s = this._s,\r
3869 i, j, progStr, buffered = 0,\r
3870 isProgress = (e.type === 'progress'),\r
3871 ranges = e.target.buffered,\r
3872 // firefox 3.6 implements e.loaded/total (bytes)\r
3873 loaded = (e.loaded||0),\r
3874 total = (e.total||1);\r
3875\r
3876 // reset the "buffered" (loaded byte ranges) array\r
3877 s.buffered = [];\r
3878\r
3879 if (ranges && ranges.length) {\r
3880\r
3881 // if loaded is 0, try TimeRanges implementation as % of load\r
3882 // https://developer.mozilla.org/en/DOM/TimeRanges\r
3883\r
3884 // re-build "buffered" array\r
3885 // HTML5 returns seconds. SM2 API uses msec for setPosition() etc., whether Flash or HTML5.\r
3886 for (i=0, j=ranges.length; i<j; i++) {\r
3887 s.buffered.push({\r
3888 'start': ranges.start(i) * msecScale,\r
3889 'end': ranges.end(i) * msecScale\r
3890 });\r
3891 }\r
3892\r
3893 // use the last value locally\r
3894 buffered = (ranges.end(0) - ranges.start(0)) * msecScale;\r
3895\r
3896 // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges\r
3897 loaded = Math.min(1, buffered/(e.target.duration*msecScale));\r
3898\r
3899 // <d>\r
3900 if (isProgress && ranges.length > 1) {\r
3901 progStr = [];\r
3902 j = ranges.length;\r
3903 for (i=0; i<j; i++) {\r
3904 progStr.push(e.target.buffered.start(i)*msecScale +'-'+ e.target.buffered.end(i)*msecScale);\r
3905 }\r
3906 sm2._wD(this._s.id + ': progress, timeRanges: ' + progStr.join(', '));\r
3907 }\r
3908\r
3909 if (isProgress && !isNaN(loaded)) {\r
3910 sm2._wD(this._s.id + ': progress, ' + Math.floor(loaded*100) + '% loaded');\r
3911 }\r
3912 // </d>\r
3913\r
3914 }\r
3915\r
3916 if (!isNaN(loaded)) {\r
3917\r
3918 // if progress, likely not buffering\r
3919 s._onbufferchange(0);\r
3920 // TODO: prevent calls with duplicate values.\r
3921 s._whileloading(loaded, total, s._get_html5_duration());\r
3922 if (loaded && total && loaded === total) {\r
3923 // in case "onload" doesn't fire (eg. gecko 1.9.2)\r
3924 html5_events.canplaythrough.call(this, e);\r
3925 }\r
3926\r
3927 }\r
3928\r
3929 }),\r
3930\r
3931 ratechange: html5_event(function() {\r
3932\r
3933 sm2._wD(this._s.id + ': ratechange');\r
3934\r
3935 }),\r
3936\r
3937 suspend: html5_event(function(e) {\r
3938\r
3939 // download paused/stopped, may have finished (eg. onload)\r
3940 var s = this._s;\r
3941\r
3942 sm2._wD(this._s.id + ': suspend');\r
3943 html5_events.progress.call(this, e);\r
3944 s._onsuspend();\r
3945\r
3946 }),\r
3947\r
3948 stalled: html5_event(function() {\r
3949\r
3950 sm2._wD(this._s.id + ': stalled');\r
3951\r
3952 }),\r
3953\r
3954 timeupdate: html5_event(function() {\r
3955\r
3956 this._s._onTimer();\r
3957\r
3958 }),\r
3959\r
3960 waiting: html5_event(function() {\r
3961\r
3962 var s = this._s;\r
3963\r
3964 // see also: seeking\r
3965 sm2._wD(this._s.id + ': waiting');\r
3966\r
3967 // playback faster than download rate, etc.\r
3968 s._onbufferchange(1);\r
3969\r
3970 })\r
3971\r
3972 };\r
3973\r
3974 html5OK = function(iO) {\r
3975\r
3976 // playability test based on URL or MIME type\r
3977\r
3978 var result;\r
3979\r
3980 if (!iO || (!iO.type && !iO.url && !iO.serverURL)) {\r
3981\r
3982 // nothing to check\r
3983 result = false;\r
3984\r
3985 } else if (iO.serverURL || (iO.type && preferFlashCheck(iO.type))) {\r
3986\r
3987 // RTMP, or preferring flash\r
3988 result = false;\r
3989\r
3990 } else {\r
3991\r
3992 // Use type, if specified. Pass data: URIs to HTML5. If HTML5-only mode, no other options, so just give 'er\r
3993 result = ((iO.type ? html5CanPlay({type:iO.type}) : html5CanPlay({url:iO.url}) || sm2.html5Only || iO.url.match(/data\:/i)));\r
3994\r
3995 }\r
3996\r
3997 return result;\r
3998\r
3999 };\r
4000\r
4001 html5Unload = function(oAudio) {\r
4002\r
4003 /**\r
4004 * Internal method: Unload media, and cancel any current/pending network requests.\r
4005 * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.\r
4006 * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media\r
4007 * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.\r
4008 * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.\r
4009 */\r
4010\r
4011 var url;\r
4012\r
4013 if (oAudio) {\r
4014\r
4015 // Firefox and Chrome accept short WAVe data: URIs. Chome dislikes audio/wav, but accepts audio/wav for data: MIME.\r
4016 // Desktop Safari complains / fails on data: URI, so it gets about:blank.\r
4017 url = (isSafari ? emptyURL : (sm2.html5.canPlayType('audio/wav') ? emptyWAV : emptyURL));\r
4018\r
4019 oAudio.src = url;\r
4020\r
4021 // reset some state, too\r
4022 if (oAudio._called_unload !== undefined) {\r
4023 oAudio._called_load = false;\r
4024 }\r
4025\r
4026 }\r
4027\r
4028 if (useGlobalHTML5Audio) {\r
4029\r
4030 // ensure URL state is trashed, also\r
4031 lastGlobalHTML5URL = null;\r
4032\r
4033 }\r
4034\r
4035 return url;\r
4036\r
4037 };\r
4038\r
4039 html5CanPlay = function(o) {\r
4040\r
4041 /**\r
4042 * Try to find MIME, test and return truthiness\r
4043 * o = {\r
4044 * url: '/path/to/an.mp3',\r
4045 * type: 'audio/mp3'\r
4046 * }\r
4047 */\r
4048\r
4049 if (!sm2.useHTML5Audio || !sm2.hasHTML5) {\r
4050 return false;\r
4051 }\r
4052\r
4053 var url = (o.url || null),\r
4054 mime = (o.type || null),\r
4055 aF = sm2.audioFormats,\r
4056 result,\r
4057 offset,\r
4058 fileExt,\r
4059 item;\r
4060\r
4061 // account for known cases like audio/mp3\r
4062\r
4063 if (mime && sm2.html5[mime] !== _undefined) {\r
4064 return (sm2.html5[mime] && !preferFlashCheck(mime));\r
4065 }\r
4066\r
4067 if (!html5Ext) {\r
4068 html5Ext = [];\r
4069 for (item in aF) {\r
4070 if (aF.hasOwnProperty(item)) {\r
4071 html5Ext.push(item);\r
4072 if (aF[item].related) {\r
4073 html5Ext = html5Ext.concat(aF[item].related);\r
4074 }\r
4075 }\r
4076 }\r
4077 html5Ext = new RegExp('\\.('+html5Ext.join('|')+')(\\?.*)?$','i');\r
4078 }\r
4079\r
4080 // TODO: Strip URL queries, etc.\r
4081 fileExt = (url ? url.toLowerCase().match(html5Ext) : null);\r
4082\r
4083 if (!fileExt || !fileExt.length) {\r
4084 if (!mime) {\r
4085 result = false;\r
4086 } else {\r
4087 // audio/mp3 -> mp3, result should be known\r
4088 offset = mime.indexOf(';');\r
4089 // strip "audio/X; codecs..."\r
4090 fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6);\r
4091 }\r
4092 } else {\r
4093 // match the raw extension name - "mp3", for example\r
4094 fileExt = fileExt[1];\r
4095 }\r
4096\r
4097 if (fileExt && sm2.html5[fileExt] !== _undefined) {\r
4098 // result known\r
4099 result = (sm2.html5[fileExt] && !preferFlashCheck(fileExt));\r
4100 } else {\r
4101 mime = 'audio/'+fileExt;\r
4102 result = sm2.html5.canPlayType({type:mime});\r
4103 sm2.html5[fileExt] = result;\r
4104 // sm2._wD('canPlayType, found result: ' + result);\r
4105 result = (result && sm2.html5[mime] && !preferFlashCheck(mime));\r
4106 }\r
4107\r
4108 return result;\r
4109\r
4110 };\r
4111\r
4112 testHTML5 = function() {\r
4113\r
4114 /**\r
4115 * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on\r
4116 * assigns results to html5[] and flash[].\r
4117 */\r
4118\r
4119 if (!sm2.useHTML5Audio || !sm2.hasHTML5) {\r
4120 // without HTML5, we need Flash.\r
4121 sm2.html5.usingFlash = true;\r
4122 needsFlash = true;\r
4123 return false;\r
4124 }\r
4125\r
4126 // double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/\r
4127 var a = (Audio !== _undefined ? (isOpera && opera.version() < 10 ? new Audio(null) : new Audio()) : null),\r
4128 item, lookup, support = {}, aF, i;\r
4129\r
4130 function cp(m) {\r
4131\r
4132 var canPlay, j,\r
4133 result = false,\r
4134 isOK = false;\r
4135\r
4136 if (!a || typeof a.canPlayType !== 'function') {\r
4137 return result;\r
4138 }\r
4139\r
4140 if (m instanceof Array) {\r
4141 // iterate through all mime types, return any successes\r
4142 for (i=0, j=m.length; i<j; i++) {\r
4143 if (sm2.html5[m[i]] || a.canPlayType(m[i]).match(sm2.html5Test)) {\r
4144 isOK = true;\r
4145 sm2.html5[m[i]] = true;\r
4146 // note flash support, too\r
4147 sm2.flash[m[i]] = !!(m[i].match(flashMIME));\r
4148 }\r
4149 }\r
4150 result = isOK;\r
4151 } else {\r
4152 canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);\r
4153 result = !!(canPlay && (canPlay.match(sm2.html5Test)));\r
4154 }\r
4155\r
4156 return result;\r
4157\r
4158 }\r
4159\r
4160 // test all registered formats + codecs\r
4161\r
4162 aF = sm2.audioFormats;\r
4163\r
4164 for (item in aF) {\r
4165\r
4166 if (aF.hasOwnProperty(item)) {\r
4167\r
4168 lookup = 'audio/' + item;\r
4169\r
4170 support[item] = cp(aF[item].type);\r
4171\r
4172 // write back generic type too, eg. audio/mp3\r
4173 support[lookup] = support[item];\r
4174\r
4175 // assign flash\r
4176 if (item.match(flashMIME)) {\r
4177\r
4178 sm2.flash[item] = true;\r
4179 sm2.flash[lookup] = true;\r
4180\r
4181 } else {\r
4182\r
4183 sm2.flash[item] = false;\r
4184 sm2.flash[lookup] = false;\r
4185\r
4186 }\r
4187\r
4188 // assign result to related formats, too\r
4189\r
4190 if (aF[item] && aF[item].related) {\r
4191\r
4192 for (i=aF[item].related.length-1; i >= 0; i--) {\r
4193\r
4194 // eg. audio/m4a\r
4195 support['audio/'+aF[item].related[i]] = support[item];\r
4196 sm2.html5[aF[item].related[i]] = support[item];\r
4197 sm2.flash[aF[item].related[i]] = support[item];\r
4198\r
4199 }\r
4200\r
4201 }\r
4202\r
4203 }\r
4204\r
4205 }\r
4206\r
4207 support.canPlayType = (a?cp:null);\r
4208 sm2.html5 = mixin(sm2.html5, support);\r
4209\r
4210 sm2.html5.usingFlash = featureCheck();\r
4211 needsFlash = sm2.html5.usingFlash;\r
4212\r
4213 return true;\r
4214\r
4215 };\r
4216\r
4217 strings = {\r
4218\r
4219 // <d>\r
4220 notReady: 'Unavailable - wait until onready() has fired.',\r
4221 notOK: 'Audio support is not available.',\r
4222 domError: sm + 'exception caught while appending SWF to DOM.',\r
4223 spcWmode: 'Removing wmode, preventing known SWF loading issue(s)',\r
4224 swf404: smc + 'Verify that %s is a valid path.',\r
4225 tryDebug: 'Try ' + sm + '.debugFlash = true for more security details (output goes to SWF.)',\r
4226 checkSWF: 'See SWF output for more debug info.',\r
4227 localFail: smc + 'Non-HTTP page (' + doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/',\r
4228 waitFocus: smc + 'Special case: Waiting for SWF to load with window focus...',\r
4229 waitForever: smc + 'Waiting indefinitely for Flash (will recover if unblocked)...',\r
4230 waitSWF: smc + 'Waiting for 100% SWF load...',\r
4231 needFunction: smc + 'Function object expected for %s',\r
4232 badID: 'Sound ID "%s" should be a string, starting with a non-numeric character',\r
4233 currentObj: smc + '_debug(): Current sound objects',\r
4234 waitOnload: smc + 'Waiting for window.onload()',\r
4235 docLoaded: smc + 'Document already loaded',\r
4236 onload: smc + 'initComplete(): calling soundManager.onload()',\r
4237 onloadOK: sm + '.onload() complete',\r
4238 didInit: smc + 'init(): Already called?',\r
4239 secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',\r
4240 badRemove: smc + 'Failed to remove Flash node.',\r
4241 shutdown: sm + '.disable(): Shutting down',\r
4242 queue: smc + 'Queueing %s handler',\r
4243 smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',\r
4244 fbTimeout: 'No flash response, applying .'+swfCSS.swfTimedout+' CSS...',\r
4245 fbLoaded: 'Flash loaded',\r
4246 fbHandler: smc + 'flashBlockHandler()',\r
4247 manURL: 'SMSound.load(): Using manually-assigned URL',\r
4248 onURL: sm + '.load(): current URL already assigned.',\r
4249 badFV: sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',\r
4250 as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',\r
4251 noNSLoop: 'Note: Looping not implemented for MovieStar formats',\r
4252 needfl9: 'Note: Switching to flash 9, required for MP4 formats.',\r
4253 mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',\r
4254 needFlash: smc + 'Fatal error: Flash is needed to play some required formats, but is not available.',\r
4255 gotFocus: smc + 'Got window focus.',\r
4256 policy: 'Enabling usePolicyFile for data access',\r
4257 setup: sm + '.setup(): allowed parameters: %s',\r
4258 setupError: sm + '.setup(): "%s" cannot be assigned with this method.',\r
4259 setupUndef: sm + '.setup(): Could not find option "%s"',\r
4260 setupLate: sm + '.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().',\r
4261 noURL: smc + 'Flash URL required. Call soundManager.setup({url:...}) to get started.',\r
4262 sm2Loaded: 'SoundManager 2: Ready.',\r
4263 reset: sm + '.reset(): Removing event callbacks',\r
4264 mobileUA: 'Mobile UA detected, preferring HTML5 by default.',\r
4265 globalHTML5: 'Using singleton HTML5 Audio() pattern for this device.'\r
4266 // </d>\r
4267\r
4268 };\r
4269\r
4270 str = function() {\r
4271\r
4272 // internal string replace helper.\r
4273 // arguments: o [,items to replace]\r
4274 // <d>\r
4275\r
4276 var args,\r
4277 i, j, o,\r
4278 sstr;\r
4279\r
4280 // real array, please\r
4281 args = slice.call(arguments);\r
4282\r
4283 // first argument\r
4284 o = args.shift();\r
4285\r
4286 sstr = (strings && strings[o] ? strings[o] : '');\r
4287\r
4288 if (sstr && args && args.length) {\r
4289 for (i = 0, j = args.length; i < j; i++) {\r
4290 sstr = sstr.replace('%s', args[i]);\r
4291 }\r
4292 }\r
4293\r
4294 return sstr;\r
4295 // </d>\r
4296\r
4297 };\r
4298\r
4299 loopFix = function(sOpt) {\r
4300\r
4301 // flash 8 requires stream = false for looping to work\r
4302 if (fV === 8 && sOpt.loops > 1 && sOpt.stream) {\r
4303 _wDS('as2loop');\r
4304 sOpt.stream = false;\r
4305 }\r
4306\r
4307 return sOpt;\r
4308\r
4309 };\r
4310\r
4311 policyFix = function(sOpt, sPre) {\r
4312\r
4313 if (sOpt && !sOpt.usePolicyFile && (sOpt.onid3 || sOpt.usePeakData || sOpt.useWaveformData || sOpt.useEQData)) {\r
4314 sm2._wD((sPre || '') + str('policy'));\r
4315 sOpt.usePolicyFile = true;\r
4316 }\r
4317\r
4318 return sOpt;\r
4319\r
4320 };\r
4321\r
4322 complain = function(sMsg) {\r
4323\r
4324 // <d>\r
4325 if (hasConsole && console.warn !== _undefined) {\r
4326 console.warn(sMsg);\r
4327 } else {\r
4328 sm2._wD(sMsg);\r
4329 }\r
4330 // </d>\r
4331\r
4332 };\r
4333\r
4334 doNothing = function() {\r
4335\r
4336 return false;\r
4337\r
4338 };\r
4339\r
4340 disableObject = function(o) {\r
4341\r
4342 var oProp;\r
4343\r
4344 for (oProp in o) {\r
4345 if (o.hasOwnProperty(oProp) && typeof o[oProp] === 'function') {\r
4346 o[oProp] = doNothing;\r
4347 }\r
4348 }\r
4349\r
4350 oProp = null;\r
4351\r
4352 };\r
4353\r
4354 failSafely = function(bNoDisable) {\r
4355\r
4356 // general failure exception handler\r
4357\r
4358 if (bNoDisable === _undefined) {\r
4359 bNoDisable = false;\r
4360 }\r
4361\r
4362 if (disabled || bNoDisable) {\r
4363 sm2.disable(bNoDisable);\r
4364 }\r
4365\r
4366 };\r
4367\r
4368 normalizeMovieURL = function(smURL) {\r
4369\r
4370 var urlParams = null, url;\r
4371\r
4372 if (smURL) {\r
4373 if (smURL.match(/\.swf(\?.*)?$/i)) {\r
4374 urlParams = smURL.substr(smURL.toLowerCase().lastIndexOf('.swf?') + 4);\r
4375 if (urlParams) {\r
4376 // assume user knows what they're doing\r
4377 return smURL;\r
4378 }\r
4379 } else if (smURL.lastIndexOf('/') !== smURL.length - 1) {\r
4380 // append trailing slash, if needed\r
4381 smURL += '/';\r
4382 }\r
4383 }\r
4384\r
4385 url = (smURL && smURL.lastIndexOf('/') !== - 1 ? smURL.substr(0, smURL.lastIndexOf('/') + 1) : './') + sm2.movieURL;\r
4386\r
4387 if (sm2.noSWFCache) {\r
4388 url += ('?ts=' + new Date().getTime());\r
4389 }\r
4390\r
4391 return url;\r
4392\r
4393 };\r
4394\r
4395 setVersionInfo = function() {\r
4396\r
4397 // short-hand for internal use\r
4398\r
4399 fV = parseInt(sm2.flashVersion, 10);\r
4400\r
4401 if (fV !== 8 && fV !== 9) {\r
4402 sm2._wD(str('badFV', fV, defaultFlashVersion));\r
4403 sm2.flashVersion = fV = defaultFlashVersion;\r
4404 }\r
4405\r
4406 // debug flash movie, if applicable\r
4407\r
4408 var isDebug = (sm2.debugMode || sm2.debugFlash?'_debug.swf':'.swf');\r
4409\r
4410 if (sm2.useHTML5Audio && !sm2.html5Only && sm2.audioFormats.mp4.required && fV < 9) {\r
4411 sm2._wD(str('needfl9'));\r
4412 sm2.flashVersion = fV = 9;\r
4413 }\r
4414\r
4415 sm2.version = sm2.versionNumber + (sm2.html5Only?' (HTML5-only mode)':(fV === 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));\r
4416\r
4417 // set up default options\r
4418 if (fV > 8) {\r
4419 // +flash 9 base options\r
4420 sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.flash9Options);\r
4421 sm2.features.buffering = true;\r
4422 // +moviestar support\r
4423 sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.movieStarOptions);\r
4424 sm2.filePatterns.flash9 = new RegExp('\\.(mp3|' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');\r
4425 sm2.features.movieStar = true;\r
4426 } else {\r
4427 sm2.features.movieStar = false;\r
4428 }\r
4429\r
4430 // regExp for flash canPlay(), etc.\r
4431 sm2.filePattern = sm2.filePatterns[(fV !== 8?'flash9':'flash8')];\r
4432\r
4433 // if applicable, use _debug versions of SWFs\r
4434 sm2.movieURL = (fV === 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug);\r
4435\r
4436 sm2.features.peakData = sm2.features.waveformData = sm2.features.eqData = (fV > 8);\r
4437\r
4438 };\r
4439\r
4440 setPolling = function(bPolling, bHighPerformance) {\r
4441\r
4442 if (!flash) {\r
4443 return false;\r
4444 }\r
4445\r
4446 flash._setPolling(bPolling, bHighPerformance);\r
4447\r
4448 };\r
4449\r
4450 initDebug = function() {\r
4451\r
4452 // starts debug mode, creating output <div> for UAs without console object\r
4453\r
4454 // allow force of debug mode via URL\r
4455 // <d>\r
4456 if (sm2.debugURLParam.test(wl)) {\r
4457 sm2.debugMode = true;\r
4458 }\r
4459\r
4460 if (id(sm2.debugID)) {\r
4461 return false;\r
4462 }\r
4463\r
4464 var oD, oDebug, oTarget, oToggle, tmp;\r
4465\r
4466 if (sm2.debugMode && !id(sm2.debugID) && (!hasConsole || !sm2.useConsole || !sm2.consoleOnly)) {\r
4467\r
4468 oD = doc.createElement('div');\r
4469 oD.id = sm2.debugID + '-toggle';\r
4470\r
4471 oToggle = {\r
4472 'position': 'fixed',\r
4473 'bottom': '0px',\r
4474 'right': '0px',\r
4475 'width': '1.2em',\r
4476 'height': '1.2em',\r
4477 'lineHeight': '1.2em',\r
4478 'margin': '2px',\r
4479 'textAlign': 'center',\r
4480 'border': '1px solid #999',\r
4481 'cursor': 'pointer',\r
4482 'background': '#fff',\r
4483 'color': '#333',\r
4484 'zIndex': 10001\r
4485 };\r
4486\r
4487 oD.appendChild(doc.createTextNode('-'));\r
4488 oD.onclick = toggleDebug;\r
4489 oD.title = 'Toggle SM2 debug console';\r
4490\r
4491 if (ua.match(/msie 6/i)) {\r
4492 oD.style.position = 'absolute';\r
4493 oD.style.cursor = 'hand';\r
4494 }\r
4495\r
4496 for (tmp in oToggle) {\r
4497 if (oToggle.hasOwnProperty(tmp)) {\r
4498 oD.style[tmp] = oToggle[tmp];\r
4499 }\r
4500 }\r
4501\r
4502 oDebug = doc.createElement('div');\r
4503 oDebug.id = sm2.debugID;\r
4504 oDebug.style.display = (sm2.debugMode?'block':'none');\r
4505\r
4506 if (sm2.debugMode && !id(oD.id)) {\r
4507 try {\r
4508 oTarget = getDocument();\r
4509 oTarget.appendChild(oD);\r
4510 } catch(e2) {\r
4511 throw new Error(str('domError')+' \n'+e2.toString());\r
4512 }\r
4513 oTarget.appendChild(oDebug);\r
4514 }\r
4515\r
4516 }\r
4517\r
4518 oTarget = null;\r
4519 // </d>\r
4520\r
4521 };\r
4522\r
4523 idCheck = this.getSoundById;\r
4524\r
4525 // <d>\r
4526 _wDS = function(o, errorLevel) {\r
4527\r
4528 return (!o ? '' : sm2._wD(str(o), errorLevel));\r
4529\r
4530 };\r
4531\r
4532 toggleDebug = function() {\r
4533\r
4534 var o = id(sm2.debugID),\r
4535 oT = id(sm2.debugID + '-toggle');\r
4536\r
4537 if (!o) {\r
4538 return false;\r
4539 }\r
4540\r
4541 if (debugOpen) {\r
4542 // minimize\r
4543 oT.innerHTML = '+';\r
4544 o.style.display = 'none';\r
4545 } else {\r
4546 oT.innerHTML = '-';\r
4547 o.style.display = 'block';\r
4548 }\r
4549\r
4550 debugOpen = !debugOpen;\r
4551\r
4552 };\r
4553\r
4554 debugTS = function(sEventType, bSuccess, sMessage) {\r
4555\r
4556 // troubleshooter debug hooks\r
4557\r
4558 if (window.sm2Debugger !== _undefined) {\r
4559 try {\r
4560 sm2Debugger.handleEvent(sEventType, bSuccess, sMessage);\r
4561 } catch(e) {\r
4562 // oh well\r
4563 return false;\r
4564 }\r
4565 }\r
4566\r
4567 return true;\r
4568\r
4569 };\r
4570 // </d>\r
4571\r
4572 getSWFCSS = function() {\r
4573\r
4574 var css = [];\r
4575\r
4576 if (sm2.debugMode) {\r
4577 css.push(swfCSS.sm2Debug);\r
4578 }\r
4579\r
4580 if (sm2.debugFlash) {\r
4581 css.push(swfCSS.flashDebug);\r
4582 }\r
4583\r
4584 if (sm2.useHighPerformance) {\r
4585 css.push(swfCSS.highPerf);\r
4586 }\r
4587\r
4588 return css.join(' ');\r
4589\r
4590 };\r
4591\r
4592 flashBlockHandler = function() {\r
4593\r
4594 // *possible* flash block situation.\r
4595\r
4596 var name = str('fbHandler'),\r
4597 p = sm2.getMoviePercent(),\r
4598 css = swfCSS,\r
4599 error = {type:'FLASHBLOCK'};\r
4600\r
4601 if (sm2.html5Only) {\r
4602 // no flash, or unused\r
4603 return false;\r
4604 }\r
4605\r
4606 if (!sm2.ok()) {\r
4607\r
4608 if (needsFlash) {\r
4609 // make the movie more visible, so user can fix\r
4610 sm2.oMC.className = getSWFCSS() + ' ' + css.swfDefault + ' ' + (p === null?css.swfTimedout:css.swfError);\r
4611 sm2._wD(name + ': ' + str('fbTimeout') + (p ? ' (' + str('fbLoaded') + ')' : ''));\r
4612 }\r
4613\r
4614 sm2.didFlashBlock = true;\r
4615\r
4616 // fire onready(), complain lightly\r
4617 processOnEvents({type:'ontimeout', ignoreInit:true, error:error});\r
4618 catchError(error);\r
4619\r
4620 } else {\r
4621\r
4622 // SM2 loaded OK (or recovered)\r
4623\r
4624 // <d>\r
4625 if (sm2.didFlashBlock) {\r
4626 sm2._wD(name + ': Unblocked');\r
4627 }\r
4628 // </d>\r
4629\r
4630 if (sm2.oMC) {\r
4631 sm2.oMC.className = [getSWFCSS(), css.swfDefault, css.swfLoaded + (sm2.didFlashBlock?' '+css.swfUnblocked:'')].join(' ');\r
4632 }\r
4633\r
4634 }\r
4635\r
4636 };\r
4637\r
4638 addOnEvent = function(sType, oMethod, oScope) {\r
4639\r
4640 if (on_queue[sType] === _undefined) {\r
4641 on_queue[sType] = [];\r
4642 }\r
4643\r
4644 on_queue[sType].push({\r
4645 'method': oMethod,\r
4646 'scope': (oScope || null),\r
4647 'fired': false\r
4648 });\r
4649\r
4650 };\r
4651\r
4652 processOnEvents = function(oOptions) {\r
4653\r
4654 // if unspecified, assume OK/error\r
4655\r
4656 if (!oOptions) {\r
4657 oOptions = {\r
4658 type: (sm2.ok() ? 'onready' : 'ontimeout')\r
4659 };\r
4660 }\r
4661\r
4662 if (!didInit && oOptions && !oOptions.ignoreInit) {\r
4663 // not ready yet.\r
4664 return false;\r
4665 }\r
4666\r
4667 if (oOptions.type === 'ontimeout' && (sm2.ok() || (disabled && !oOptions.ignoreInit))) {\r
4668 // invalid case\r
4669 return false;\r
4670 }\r
4671\r
4672 var status = {\r
4673 success: (oOptions && oOptions.ignoreInit?sm2.ok():!disabled)\r
4674 },\r
4675\r
4676 // queue specified by type, or none\r
4677 srcQueue = (oOptions && oOptions.type?on_queue[oOptions.type]||[]:[]),\r
4678\r
4679 queue = [], i, j,\r
4680 args = [status],\r
4681 canRetry = (needsFlash && !sm2.ok());\r
4682\r
4683 if (oOptions.error) {\r
4684 args[0].error = oOptions.error;\r
4685 }\r
4686\r
4687 for (i = 0, j = srcQueue.length; i < j; i++) {\r
4688 if (srcQueue[i].fired !== true) {\r
4689 queue.push(srcQueue[i]);\r
4690 }\r
4691 }\r
4692\r
4693 if (queue.length) {\r
4694 // sm2._wD(sm + ': Firing ' + queue.length + ' ' + oOptions.type + '() item' + (queue.length === 1 ? '' : 's'));\r
4695 for (i = 0, j = queue.length; i < j; i++) {\r
4696 if (queue[i].scope) {\r
4697 queue[i].method.apply(queue[i].scope, args);\r
4698 } else {\r
4699 queue[i].method.apply(this, args);\r
4700 }\r
4701 if (!canRetry) {\r
4702 // useFlashBlock and SWF timeout case doesn't count here.\r
4703 queue[i].fired = true;\r
4704 }\r
4705 }\r
4706 }\r
4707\r
4708 return true;\r
4709\r
4710 };\r
4711\r
4712 initUserOnload = function() {\r
4713\r
4714 window.setTimeout(function() {\r
4715\r
4716 if (sm2.useFlashBlock) {\r
4717 flashBlockHandler();\r
4718 }\r
4719\r
4720 processOnEvents();\r
4721\r
4722 // call user-defined "onload", scoped to window\r
4723\r
4724 if (typeof sm2.onload === 'function') {\r
4725 _wDS('onload', 1);\r
4726 sm2.onload.apply(window);\r
4727 _wDS('onloadOK', 1);\r
4728 }\r
4729\r
4730 if (sm2.waitForWindowLoad) {\r
4731 event.add(window, 'load', initUserOnload);\r
4732 }\r
4733\r
4734 },1);\r
4735\r
4736 };\r
4737\r
4738 detectFlash = function() {\r
4739\r
4740 // hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt\r
4741\r
4742 if (hasFlash !== _undefined) {\r
4743 // this work has already been done.\r
4744 return hasFlash;\r
4745 }\r
4746\r
4747 var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = window.ActiveXObject;\r
4748\r
4749 if (nP && nP.length) {\r
4750 type = 'application/x-shockwave-flash';\r
4751 types = n.mimeTypes;\r
4752 if (types && types[type] && types[type].enabledPlugin && types[type].enabledPlugin.description) {\r
4753 hasPlugin = true;\r
4754 }\r
4755 } else if (AX !== _undefined && !ua.match(/MSAppHost/i)) {\r
4756 // Windows 8 Store Apps (MSAppHost) are weird (compatibility?) and won't complain here, but will barf if Flash/ActiveX object is appended to the DOM.\r
4757 try {\r
4758 obj = new AX('ShockwaveFlash.ShockwaveFlash');\r
4759 } catch(e) {\r
4760 // oh well\r
4761 obj = null;\r
4762 }\r
4763 hasPlugin = (!!obj);\r
4764 // cleanup, because it is ActiveX after all\r
4765 obj = null;\r
4766 }\r
4767\r
4768 hasFlash = hasPlugin;\r
4769\r
4770 return hasPlugin;\r
4771\r
4772 };\r
4773\r
4774 featureCheck = function() {\r
4775\r
4776 var flashNeeded,\r
4777 item,\r
4778 formats = sm2.audioFormats,\r
4779 // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.\r
4780 isSpecial = (is_iDevice && !!(ua.match(/os (1|2|3_0|3_1)/i)));\r
4781\r
4782 if (isSpecial) {\r
4783\r
4784 // has Audio(), but is broken; let it load links directly.\r
4785 sm2.hasHTML5 = false;\r
4786\r
4787 // ignore flash case, however\r
4788 sm2.html5Only = true;\r
4789\r
4790 // hide the SWF, if present\r
4791 if (sm2.oMC) {\r
4792 sm2.oMC.style.display = 'none';\r
4793 }\r
4794\r
4795 } else {\r
4796\r
4797 if (sm2.useHTML5Audio) {\r
4798\r
4799 if (!sm2.html5 || !sm2.html5.canPlayType) {\r
4800 sm2._wD('SoundManager: No HTML5 Audio() support detected.');\r
4801 sm2.hasHTML5 = false;\r
4802 }\r
4803\r
4804 // <d>\r
4805 if (isBadSafari) {\r
4806 sm2._wD(smc + 'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - ' + (!hasFlash ?' would use flash fallback for MP3/MP4, but none detected.' : 'will use flash fallback for MP3/MP4, if available'), 1);\r
4807 }\r
4808 // </d>\r
4809\r
4810 }\r
4811\r
4812 }\r
4813\r
4814 if (sm2.useHTML5Audio && sm2.hasHTML5) {\r
4815\r
4816 // sort out whether flash is optional, required or can be ignored.\r
4817\r
4818 // innocent until proven guilty.\r
4819 canIgnoreFlash = true;\r
4820\r
4821 for (item in formats) {\r
4822 if (formats.hasOwnProperty(item)) {\r
4823 if (formats[item].required) {\r
4824 if (!sm2.html5.canPlayType(formats[item].type)) {\r
4825 // 100% HTML5 mode is not possible.\r
4826 canIgnoreFlash = false;\r
4827 flashNeeded = true;\r
4828 } else if (sm2.preferFlash && (sm2.flash[item] || sm2.flash[formats[item].type])) {\r
4829 // flash may be required, or preferred for this format.\r
4830 flashNeeded = true;\r
4831 }\r
4832 }\r
4833 }\r
4834 }\r
4835\r
4836 }\r
4837\r
4838 // sanity check...\r
4839 if (sm2.ignoreFlash) {\r
4840 flashNeeded = false;\r
4841 canIgnoreFlash = true;\r
4842 }\r
4843\r
4844 sm2.html5Only = (sm2.hasHTML5 && sm2.useHTML5Audio && !flashNeeded);\r
4845\r
4846 return (!sm2.html5Only);\r
4847\r
4848 };\r
4849\r
4850 parseURL = function(url) {\r
4851\r
4852 /**\r
4853 * Internal: Finds and returns the first playable URL (or failing that, the first URL.)\r
4854 * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.\r
4855 */\r
4856\r
4857 var i, j, urlResult = 0, result;\r
4858\r
4859 if (url instanceof Array) {\r
4860\r
4861 // find the first good one\r
4862 for (i=0, j=url.length; i<j; i++) {\r
4863\r
4864 if (url[i] instanceof Object) {\r
4865 // MIME check\r
4866 if (sm2.canPlayMIME(url[i].type)) {\r
4867 urlResult = i;\r
4868 break;\r
4869 }\r
4870\r
4871 } else if (sm2.canPlayURL(url[i])) {\r
4872 // URL string check\r
4873 urlResult = i;\r
4874 break;\r
4875 }\r
4876\r
4877 }\r
4878\r
4879 // normalize to string\r
4880 if (url[urlResult].url) {\r
4881 url[urlResult] = url[urlResult].url;\r
4882 }\r
4883\r
4884 result = url[urlResult];\r
4885\r
4886 } else {\r
4887\r
4888 // single URL case\r
4889 result = url;\r
4890\r
4891 }\r
4892\r
4893 return result;\r
4894\r
4895 };\r
4896\r
4897\r
4898 startTimer = function(oSound) {\r
4899\r
4900 /**\r
4901 * attach a timer to this sound, and start an interval if needed\r
4902 */\r
4903\r
4904 if (!oSound._hasTimer) {\r
4905\r
4906 oSound._hasTimer = true;\r
4907\r
4908 if (!mobileHTML5 && sm2.html5PollingInterval) {\r
4909\r
4910 if (h5IntervalTimer === null && h5TimerCount === 0) {\r
4911\r
4912 h5IntervalTimer = setInterval(timerExecute, sm2.html5PollingInterval);\r
4913\r
4914 }\r
4915\r
4916 h5TimerCount++;\r
4917\r
4918 }\r
4919\r
4920 }\r
4921\r
4922 };\r
4923\r
4924 stopTimer = function(oSound) {\r
4925\r
4926 /**\r
4927 * detach a timer\r
4928 */\r
4929\r
4930 if (oSound._hasTimer) {\r
4931\r
4932 oSound._hasTimer = false;\r
4933\r
4934 if (!mobileHTML5 && sm2.html5PollingInterval) {\r
4935\r
4936 // interval will stop itself at next execution.\r
4937\r
4938 h5TimerCount--;\r
4939\r
4940 }\r
4941\r
4942 }\r
4943\r
4944 };\r
4945\r
4946 timerExecute = function() {\r
4947\r
4948 /**\r
4949 * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)\r
4950 */\r
4951\r
4952 var i;\r
4953\r
4954 if (h5IntervalTimer !== null && !h5TimerCount) {\r
4955\r
4956 // no active timers, stop polling interval.\r
4957\r
4958 clearInterval(h5IntervalTimer);\r
4959\r
4960 h5IntervalTimer = null;\r
4961\r
4962 return false;\r
4963\r
4964 }\r
4965\r
4966 // check all HTML5 sounds with timers\r
4967\r
4968 for (i = sm2.soundIDs.length-1; i >= 0; i--) {\r
4969\r
4970 if (sm2.sounds[sm2.soundIDs[i]].isHTML5 && sm2.sounds[sm2.soundIDs[i]]._hasTimer) {\r
4971\r
4972 sm2.sounds[sm2.soundIDs[i]]._onTimer();\r
4973\r
4974 }\r
4975\r
4976 }\r
4977\r
4978 };\r
4979\r
4980 catchError = function(options) {\r
4981\r
4982 options = (options !== _undefined ? options : {});\r
4983\r
4984 if (typeof sm2.onerror === 'function') {\r
4985 sm2.onerror.apply(window, [{type:(options.type !== _undefined ? options.type : null)}]);\r
4986 }\r
4987\r
4988 if (options.fatal !== _undefined && options.fatal) {\r
4989 sm2.disable();\r
4990 }\r
4991\r
4992 };\r
4993\r
4994 badSafariFix = function() {\r
4995\r
4996 // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4\r
4997 if (!isBadSafari || !detectFlash()) {\r
4998 // doesn't apply\r
4999 return false;\r
5000 }\r
5001\r
5002 var aF = sm2.audioFormats, i, item;\r
5003\r
5004 for (item in aF) {\r
5005 if (aF.hasOwnProperty(item)) {\r
5006 if (item === 'mp3' || item === 'mp4') {\r
5007 sm2._wD(sm + ': Using flash fallback for ' + item + ' format');\r
5008 sm2.html5[item] = false;\r
5009 // assign result to related formats, too\r
5010 if (aF[item] && aF[item].related) {\r
5011 for (i = aF[item].related.length-1; i >= 0; i--) {\r
5012 sm2.html5[aF[item].related[i]] = false;\r
5013 }\r
5014 }\r
5015 }\r
5016 }\r
5017 }\r
5018\r
5019 };\r
5020\r
5021 /**\r
5022 * Pseudo-private flash/ExternalInterface methods\r
5023 * ----------------------------------------------\r
5024 */\r
5025\r
5026 this._setSandboxType = function(sandboxType) {\r
5027\r
5028 // <d>\r
5029 var sb = sm2.sandbox;\r
5030\r
5031 sb.type = sandboxType;\r
5032 sb.description = sb.types[(sb.types[sandboxType] !== _undefined?sandboxType:'unknown')];\r
5033\r
5034 if (sb.type === 'localWithFile') {\r
5035\r
5036 sb.noRemote = true;\r
5037 sb.noLocal = false;\r
5038 _wDS('secNote', 2);\r
5039\r
5040 } else if (sb.type === 'localWithNetwork') {\r
5041\r
5042 sb.noRemote = false;\r
5043 sb.noLocal = true;\r
5044\r
5045 } else if (sb.type === 'localTrusted') {\r
5046\r
5047 sb.noRemote = false;\r
5048 sb.noLocal = false;\r
5049\r
5050 }\r
5051 // </d>\r
5052\r
5053 };\r
5054\r
5055 this._externalInterfaceOK = function(swfVersion) {\r
5056\r
5057 // flash callback confirming flash loaded, EI working etc.\r
5058 // swfVersion: SWF build string\r
5059\r
5060 if (sm2.swfLoaded) {\r
5061 return false;\r
5062 }\r
5063\r
5064 var e;\r
5065\r
5066 debugTS('swf', true);\r
5067 debugTS('flashtojs', true);\r
5068 sm2.swfLoaded = true;\r
5069 tryInitOnFocus = false;\r
5070\r
5071 if (isBadSafari) {\r
5072 badSafariFix();\r
5073 }\r
5074\r
5075 // complain if JS + SWF build/version strings don't match, excluding +DEV builds\r
5076 // <d>\r
5077 if (!swfVersion || swfVersion.replace(/\+dev/i,'') !== sm2.versionNumber.replace(/\+dev/i, '')) {\r
5078\r
5079 e = sm + ': Fatal: JavaScript file build "' + sm2.versionNumber + '" does not match Flash SWF build "' + swfVersion + '" at ' + sm2.url + '. Ensure both are up-to-date.';\r
5080\r
5081 // escape flash -> JS stack so this error fires in window.\r
5082 setTimeout(function versionMismatch() {\r
5083 throw new Error(e);\r
5084 }, 0);\r
5085\r
5086 // exit, init will fail with timeout\r
5087 return false;\r
5088\r
5089 }\r
5090 // </d>\r
5091\r
5092 // IE needs a larger timeout\r
5093 setTimeout(init, isIE ? 100 : 1);\r
5094\r
5095 };\r
5096\r
5097 /**\r
5098 * Private initialization helpers\r
5099 * ------------------------------\r
5100 */\r
5101\r
5102 createMovie = function(smID, smURL) {\r
5103\r
5104 if (didAppend && appendSuccess) {\r
5105 // ignore if already succeeded\r
5106 return false;\r
5107 }\r
5108\r
5109 function initMsg() {\r
5110\r
5111 // <d>\r
5112\r
5113 var options = [],\r
5114 title,\r
5115 msg = [],\r
5116 delimiter = ' + ';\r
5117\r
5118 title = 'SoundManager ' + sm2.version + (!sm2.html5Only && sm2.useHTML5Audio ? (sm2.hasHTML5 ? ' + HTML5 audio' : ', no HTML5 audio support') : '');\r
5119\r
5120 if (!sm2.html5Only) {\r
5121\r
5122 if (sm2.preferFlash) {\r
5123 options.push('preferFlash');\r
5124 }\r
5125\r
5126 if (sm2.useHighPerformance) {\r
5127 options.push('useHighPerformance');\r
5128 }\r
5129\r
5130 if (sm2.flashPollingInterval) {\r
5131 options.push('flashPollingInterval (' + sm2.flashPollingInterval + 'ms)');\r
5132 }\r
5133\r
5134 if (sm2.html5PollingInterval) {\r
5135 options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');\r
5136 }\r
5137\r
5138 if (sm2.wmode) {\r
5139 options.push('wmode (' + sm2.wmode + ')');\r
5140 }\r
5141\r
5142 if (sm2.debugFlash) {\r
5143 options.push('debugFlash');\r
5144 }\r
5145\r
5146 if (sm2.useFlashBlock) {\r
5147 options.push('flashBlock');\r
5148 }\r
5149\r
5150 } else {\r
5151\r
5152 if (sm2.html5PollingInterval) {\r
5153 options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');\r
5154 }\r
5155\r
5156 }\r
5157\r
5158 if (options.length) {\r
5159 msg = msg.concat([options.join(delimiter)]);\r
5160 }\r
5161\r
5162 sm2._wD(title + (msg.length ? delimiter + msg.join(', ') : ''), 1);\r
5163\r
5164 showSupport();\r
5165\r
5166 // </d>\r
5167\r
5168 }\r
5169\r
5170 if (sm2.html5Only) {\r
5171\r
5172 // 100% HTML5 mode\r
5173 setVersionInfo();\r
5174\r
5175 initMsg();\r
5176 sm2.oMC = id(sm2.movieID);\r
5177 init();\r
5178\r
5179 // prevent multiple init attempts\r
5180 didAppend = true;\r
5181\r
5182 appendSuccess = true;\r
5183\r
5184 return false;\r
5185\r
5186 }\r
5187\r
5188 // flash path\r
5189 var remoteURL = (smURL || sm2.url),\r
5190 localURL = (sm2.altURL || remoteURL),\r
5191 swfTitle = 'JS/Flash audio component (SoundManager 2)',\r
5192 oTarget = getDocument(),\r
5193 extraClass = getSWFCSS(),\r
5194 isRTL = null,\r
5195 html = doc.getElementsByTagName('html')[0],\r
5196 oEmbed, oMovie, tmp, movieHTML, oEl, s, x, sClass;\r
5197\r
5198 isRTL = (html && html.dir && html.dir.match(/rtl/i));\r
5199 smID = (smID === _undefined?sm2.id:smID);\r
5200\r
5201 function param(name, value) {\r
5202 return '<param name="'+name+'" value="'+value+'" />';\r
5203 }\r
5204\r
5205 // safety check for legacy (change to Flash 9 URL)\r
5206 setVersionInfo();\r
5207 sm2.url = normalizeMovieURL(overHTTP?remoteURL:localURL);\r
5208 smURL = sm2.url;\r
5209\r
5210 sm2.wmode = (!sm2.wmode && sm2.useHighPerformance ? 'transparent' : sm2.wmode);\r
5211\r
5212 if (sm2.wmode !== null && (ua.match(/msie 8/i) || (!isIE && !sm2.useHighPerformance)) && navigator.platform.match(/win32|win64/i)) {\r
5213 /**\r
5214 * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here\r
5215 * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout\r
5216 * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)\r
5217 */\r
5218 messages.push(strings.spcWmode);\r
5219 sm2.wmode = null;\r
5220 }\r
5221\r
5222 oEmbed = {\r
5223 'name': smID,\r
5224 'id': smID,\r
5225 'src': smURL,\r
5226 'quality': 'high',\r
5227 'allowScriptAccess': sm2.allowScriptAccess,\r
5228 'bgcolor': sm2.bgColor,\r
5229 'pluginspage': http+'www.macromedia.com/go/getflashplayer',\r
5230 'title': swfTitle,\r
5231 'type': 'application/x-shockwave-flash',\r
5232 'wmode': sm2.wmode,\r
5233 // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html\r
5234 'hasPriority': 'true'\r
5235 };\r
5236\r
5237 if (sm2.debugFlash) {\r
5238 oEmbed.FlashVars = 'debug=1';\r
5239 }\r
5240\r
5241 if (!sm2.wmode) {\r
5242 // don't write empty attribute\r
5243 delete oEmbed.wmode;\r
5244 }\r
5245\r
5246 if (isIE) {\r
5247\r
5248 // IE is "special".\r
5249 oMovie = doc.createElement('div');\r
5250 movieHTML = [\r
5251 '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',\r
5252 param('movie', smURL),\r
5253 param('AllowScriptAccess', sm2.allowScriptAccess),\r
5254 param('quality', oEmbed.quality),\r
5255 (sm2.wmode? param('wmode', sm2.wmode): ''),\r
5256 param('bgcolor', sm2.bgColor),\r
5257 param('hasPriority', 'true'),\r
5258 (sm2.debugFlash ? param('FlashVars', oEmbed.FlashVars) : ''),\r
5259 '</object>'\r
5260 ].join('');\r
5261\r
5262 } else {\r
5263\r
5264 oMovie = doc.createElement('embed');\r
5265 for (tmp in oEmbed) {\r
5266 if (oEmbed.hasOwnProperty(tmp)) {\r
5267 oMovie.setAttribute(tmp, oEmbed[tmp]);\r
5268 }\r
5269 }\r
5270\r
5271 }\r
5272\r
5273 initDebug();\r
5274 extraClass = getSWFCSS();\r
5275 oTarget = getDocument();\r
5276\r
5277 if (oTarget) {\r
5278\r
5279 sm2.oMC = (id(sm2.movieID) || doc.createElement('div'));\r
5280\r
5281 if (!sm2.oMC.id) {\r
5282\r
5283 sm2.oMC.id = sm2.movieID;\r
5284 sm2.oMC.className = swfCSS.swfDefault + ' ' + extraClass;\r
5285 s = null;\r
5286 oEl = null;\r
5287\r
5288 if (!sm2.useFlashBlock) {\r
5289 if (sm2.useHighPerformance) {\r
5290 // on-screen at all times\r
5291 s = {\r
5292 'position': 'fixed',\r
5293 'width': '8px',\r
5294 'height': '8px',\r
5295 // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.\r
5296 'bottom': '0px',\r
5297 'left': '0px',\r
5298 'overflow': 'hidden'\r
5299 };\r
5300 } else {\r
5301 // hide off-screen, lower priority\r
5302 s = {\r
5303 'position': 'absolute',\r
5304 'width': '6px',\r
5305 'height': '6px',\r
5306 'top': '-9999px',\r
5307 'left': '-9999px'\r
5308 };\r
5309 if (isRTL) {\r
5310 s.left = Math.abs(parseInt(s.left,10))+'px';\r
5311 }\r
5312 }\r
5313 }\r
5314\r
5315 if (isWebkit) {\r
5316 // soundcloud-reported render/crash fix, safari 5\r
5317 sm2.oMC.style.zIndex = 10000;\r
5318 }\r
5319\r
5320 if (!sm2.debugFlash) {\r
5321 for (x in s) {\r
5322 if (s.hasOwnProperty(x)) {\r
5323 sm2.oMC.style[x] = s[x];\r
5324 }\r
5325 }\r
5326 }\r
5327\r
5328 try {\r
5329 if (!isIE) {\r
5330 sm2.oMC.appendChild(oMovie);\r
5331 }\r
5332 oTarget.appendChild(sm2.oMC);\r
5333 if (isIE) {\r
5334 oEl = sm2.oMC.appendChild(doc.createElement('div'));\r
5335 oEl.className = swfCSS.swfBox;\r
5336 oEl.innerHTML = movieHTML;\r
5337 }\r
5338 appendSuccess = true;\r
5339 } catch(e) {\r
5340 throw new Error(str('domError')+' \n'+e.toString());\r
5341 }\r
5342\r
5343 } else {\r
5344\r
5345 // SM2 container is already in the document (eg. flashblock use case)\r
5346 sClass = sm2.oMC.className;\r
5347 sm2.oMC.className = (sClass?sClass+' ':swfCSS.swfDefault) + (extraClass?' '+extraClass:'');\r
5348 sm2.oMC.appendChild(oMovie);\r
5349 if (isIE) {\r
5350 oEl = sm2.oMC.appendChild(doc.createElement('div'));\r
5351 oEl.className = swfCSS.swfBox;\r
5352 oEl.innerHTML = movieHTML;\r
5353 }\r
5354 appendSuccess = true;\r
5355\r
5356 }\r
5357\r
5358 }\r
5359\r
5360 didAppend = true;\r
5361 initMsg();\r
5362 // sm2._wD(sm + ': Trying to load ' + smURL + (!overHTTP && sm2.altURL ? ' (alternate URL)' : ''), 1);\r
5363\r
5364 return true;\r
5365\r
5366 };\r
5367\r
5368 initMovie = function() {\r
5369\r
5370 if (sm2.html5Only) {\r
5371 createMovie();\r
5372 return false;\r
5373 }\r
5374\r
5375 // attempt to get, or create, movie (may already exist)\r
5376 if (flash) {\r
5377 return false;\r
5378 }\r
5379\r
5380 if (!sm2.url) {\r
5381\r
5382 /**\r
5383 * Something isn't right - we've reached init, but the soundManager url property has not been set.\r
5384 * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.\r
5385 * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.\r
5386 */\r
5387\r
5388 _wDS('noURL');\r
5389 return false;\r
5390\r
5391 }\r
5392\r
5393 // inline markup case\r
5394 flash = sm2.getMovie(sm2.id);\r
5395\r
5396 if (!flash) {\r
5397 if (!oRemoved) {\r
5398 // try to create\r
5399 createMovie(sm2.id, sm2.url);\r
5400 } else {\r
5401 // try to re-append removed movie after reboot()\r
5402 if (!isIE) {\r
5403 sm2.oMC.appendChild(oRemoved);\r
5404 } else {\r
5405 sm2.oMC.innerHTML = oRemovedHTML;\r
5406 }\r
5407 oRemoved = null;\r
5408 didAppend = true;\r
5409 }\r
5410 flash = sm2.getMovie(sm2.id);\r
5411 }\r
5412\r
5413 if (typeof sm2.oninitmovie === 'function') {\r
5414 setTimeout(sm2.oninitmovie, 1);\r
5415 }\r
5416\r
5417 // <d>\r
5418 flushMessages();\r
5419 // </d>\r
5420\r
5421 return true;\r
5422\r
5423 };\r
5424\r
5425 delayWaitForEI = function() {\r
5426\r
5427 setTimeout(waitForEI, 1000);\r
5428\r
5429 };\r
5430\r
5431 rebootIntoHTML5 = function() {\r
5432\r
5433 // special case: try for a reboot with preferFlash: false, if 100% HTML5 mode is possible and useFlashBlock is not enabled.\r
5434\r
5435 window.setTimeout(function() {\r
5436\r
5437 complain(smc + 'useFlashBlock is false, 100% HTML5 mode is possible. Rebooting with preferFlash: false...');\r
5438\r
5439 sm2.setup({\r
5440 preferFlash: false\r
5441 }).reboot();\r
5442\r
5443 // if for some reason you want to detect this case, use an ontimeout() callback and look for html5Only and didFlashBlock == true.\r
5444 sm2.didFlashBlock = true;\r
5445\r
5446 sm2.beginDelayedInit();\r
5447\r
5448 }, 1);\r
5449\r
5450 };\r
5451\r
5452 waitForEI = function() {\r
5453\r
5454 var p,\r
5455 loadIncomplete = false;\r
5456\r
5457 if (!sm2.url) {\r
5458 // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.\r
5459 return false;\r
5460 }\r
5461\r
5462 if (waitingForEI) {\r
5463 return false;\r
5464 }\r
5465\r
5466 waitingForEI = true;\r
5467 event.remove(window, 'load', delayWaitForEI);\r
5468\r
5469 if (hasFlash && tryInitOnFocus && !isFocused) {\r
5470 // Safari won't load flash in background tabs, only when focused.\r
5471 _wDS('waitFocus');\r
5472 return false;\r
5473 }\r
5474\r
5475 if (!didInit) {\r
5476 p = sm2.getMoviePercent();\r
5477 if (p > 0 && p < 100) {\r
5478 loadIncomplete = true;\r
5479 }\r
5480 }\r
5481\r
5482 setTimeout(function() {\r
5483\r
5484 p = sm2.getMoviePercent();\r
5485\r
5486 if (loadIncomplete) {\r
5487 // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.\r
5488 waitingForEI = false;\r
5489 sm2._wD(str('waitSWF'));\r
5490 window.setTimeout(delayWaitForEI, 1);\r
5491 return false;\r
5492 }\r
5493\r
5494 // <d>\r
5495 if (!didInit) {\r
5496\r
5497 sm2._wD(sm + ': No Flash response within expected time. Likely causes: ' + (p === 0 ? 'SWF load failed, ':'') + 'Flash blocked or JS-Flash security error.' + (sm2.debugFlash?' ' + str('checkSWF'):''), 2);\r
5498\r
5499 if (!overHTTP && p) {\r
5500\r
5501 _wDS('localFail', 2);\r
5502\r
5503 if (!sm2.debugFlash) {\r
5504 _wDS('tryDebug', 2);\r
5505 }\r
5506\r
5507 }\r
5508\r
5509 if (p === 0) {\r
5510\r
5511 // if 0 (not null), probably a 404.\r
5512 sm2._wD(str('swf404', sm2.url), 1);\r
5513\r
5514 }\r
5515\r
5516 debugTS('flashtojs', false, ': Timed out' + overHTTP?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');\r
5517\r
5518 }\r
5519 // </d>\r
5520\r
5521 // give up / time-out, depending\r
5522\r
5523 if (!didInit && okToDisable) {\r
5524\r
5525 if (p === null) {\r
5526\r
5527 // SWF failed to report load progress. Possibly blocked.\r
5528\r
5529 if (sm2.useFlashBlock || sm2.flashLoadTimeout === 0) {\r
5530\r
5531 if (sm2.useFlashBlock) {\r
5532\r
5533 flashBlockHandler();\r
5534\r
5535 }\r
5536\r
5537 _wDS('waitForever');\r
5538\r
5539 } else {\r
5540\r
5541 // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.\r
5542\r
5543 if (!sm2.useFlashBlock && canIgnoreFlash) {\r
5544\r
5545 rebootIntoHTML5();\r
5546\r
5547 } else {\r
5548\r
5549 _wDS('waitForever');\r
5550\r
5551 // fire any regular registered ontimeout() listeners.\r
5552 processOnEvents({type:'ontimeout', ignoreInit: true, error: {type: 'INIT_FLASHBLOCK'}});\r
5553\r
5554 }\r
5555\r
5556 }\r
5557\r
5558 } else {\r
5559\r
5560 // SWF loaded? Shouldn't be a blocking issue, then.\r
5561\r
5562 if (sm2.flashLoadTimeout === 0) {\r
5563\r
5564 _wDS('waitForever');\r
5565\r
5566 } else {\r
5567\r
5568 if (!sm2.useFlashBlock && canIgnoreFlash) {\r
5569\r
5570 rebootIntoHTML5();\r
5571\r
5572 } else {\r
5573\r
5574 failSafely(true);\r
5575\r
5576 }\r
5577\r
5578 }\r
5579\r
5580 }\r
5581\r
5582 }\r
5583\r
5584 }, sm2.flashLoadTimeout);\r
5585\r
5586 };\r
5587\r
5588 handleFocus = function() {\r
5589\r
5590 function cleanup() {\r
5591 event.remove(window, 'focus', handleFocus);\r
5592 }\r
5593\r
5594 if (isFocused || !tryInitOnFocus) {\r
5595 // already focused, or not special Safari background tab case\r
5596 cleanup();\r
5597 return true;\r
5598 }\r
5599\r
5600 okToDisable = true;\r
5601 isFocused = true;\r
5602 _wDS('gotFocus');\r
5603\r
5604 // allow init to restart\r
5605 waitingForEI = false;\r
5606\r
5607 // kick off ExternalInterface timeout, now that the SWF has started\r
5608 delayWaitForEI();\r
5609\r
5610 cleanup();\r
5611 return true;\r
5612\r
5613 };\r
5614\r
5615 flushMessages = function() {\r
5616\r
5617 // <d>\r
5618\r
5619 // SM2 pre-init debug messages\r
5620 if (messages.length) {\r
5621 sm2._wD('SoundManager 2: ' + messages.join(' '), 1);\r
5622 messages = [];\r
5623 }\r
5624\r
5625 // </d>\r
5626\r
5627 };\r
5628\r
5629 showSupport = function() {\r
5630\r
5631 // <d>\r
5632\r
5633 flushMessages();\r
5634\r
5635 var item, tests = [];\r
5636\r
5637 if (sm2.useHTML5Audio && sm2.hasHTML5) {\r
5638 for (item in sm2.audioFormats) {\r
5639 if (sm2.audioFormats.hasOwnProperty(item)) {\r
5640 tests.push(item + ' = ' + sm2.html5[item] + (!sm2.html5[item] && needsFlash && sm2.flash[item] ? ' (using flash)' : (sm2.preferFlash && sm2.flash[item] && needsFlash ? ' (preferring flash)': (!sm2.html5[item] ? ' (' + (sm2.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));\r
5641 }\r
5642 }\r
5643 sm2._wD('SoundManager 2 HTML5 support: ' + tests.join(', '), 1);\r
5644 }\r
5645\r
5646 // </d>\r
5647\r
5648 };\r
5649\r
5650 initComplete = function(bNoDisable) {\r
5651\r
5652 if (didInit) {\r
5653 return false;\r
5654 }\r
5655\r
5656 if (sm2.html5Only) {\r
5657 // all good.\r
5658 _wDS('sm2Loaded');\r
5659 didInit = true;\r
5660 initUserOnload();\r
5661 debugTS('onload', true);\r
5662 return true;\r
5663 }\r
5664\r
5665 var wasTimeout = (sm2.useFlashBlock && sm2.flashLoadTimeout && !sm2.getMoviePercent()),\r
5666 result = true,\r
5667 error;\r
5668\r
5669 if (!wasTimeout) {\r
5670 didInit = true;\r
5671 }\r
5672\r
5673 error = {type: (!hasFlash && needsFlash ? 'NO_FLASH' : 'INIT_TIMEOUT')};\r
5674\r
5675 sm2._wD('SoundManager 2 ' + (disabled ? 'failed to load' : 'loaded') + ' (' + (disabled ? 'Flash security/load error' : 'OK') + ')', disabled ? 2: 1);\r
5676\r
5677 if (disabled || bNoDisable) {\r
5678 if (sm2.useFlashBlock && sm2.oMC) {\r
5679 sm2.oMC.className = getSWFCSS() + ' ' + (sm2.getMoviePercent() === null?swfCSS.swfTimedout:swfCSS.swfError);\r
5680 }\r
5681 processOnEvents({type:'ontimeout', error:error, ignoreInit: true});\r
5682 debugTS('onload', false);\r
5683 catchError(error);\r
5684 result = false;\r
5685 } else {\r
5686 debugTS('onload', true);\r
5687 }\r
5688\r
5689 if (!disabled) {\r
5690 if (sm2.waitForWindowLoad && !windowLoaded) {\r
5691 _wDS('waitOnload');\r
5692 event.add(window, 'load', initUserOnload);\r
5693 } else {\r
5694 // <d>\r
5695 if (sm2.waitForWindowLoad && windowLoaded) {\r
5696 _wDS('docLoaded');\r
5697 }\r
5698 // </d>\r
5699 initUserOnload();\r
5700 }\r
5701 }\r
5702\r
5703 return result;\r
5704\r
5705 };\r
5706\r
5707 /**\r
5708 * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)\r
5709 * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().\r
5710 */\r
5711\r
5712 setProperties = function() {\r
5713\r
5714 var i,\r
5715 o = sm2.setupOptions;\r
5716\r
5717 for (i in o) {\r
5718\r
5719 if (o.hasOwnProperty(i)) {\r
5720\r
5721 // assign local property if not already defined\r
5722\r
5723 if (sm2[i] === _undefined) {\r
5724\r
5725 sm2[i] = o[i];\r
5726\r
5727 } else if (sm2[i] !== o[i]) {\r
5728\r
5729 // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync\r
5730 sm2.setupOptions[i] = sm2[i];\r
5731\r
5732 }\r
5733\r
5734 }\r
5735\r
5736 }\r
5737\r
5738 };\r
5739\r
5740\r
5741 init = function() {\r
5742\r
5743 // called after onload()\r
5744\r
5745 if (didInit) {\r
5746 _wDS('didInit');\r
5747 return false;\r
5748 }\r
5749\r
5750 function cleanup() {\r
5751 event.remove(window, 'load', sm2.beginDelayedInit);\r
5752 }\r
5753\r
5754 if (sm2.html5Only) {\r
5755 if (!didInit) {\r
5756 // we don't need no steenking flash!\r
5757 cleanup();\r
5758 sm2.enabled = true;\r
5759 initComplete();\r
5760 }\r
5761 return true;\r
5762 }\r
5763\r
5764 // flash path\r
5765 initMovie();\r
5766\r
5767 try {\r
5768\r
5769 // attempt to talk to Flash\r
5770 flash._externalInterfaceTest(false);\r
5771\r
5772 // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling\r
5773 // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)\r
5774 setPolling(true, (sm2.flashPollingInterval || (sm2.useHighPerformance ? 10 : 50)));\r
5775\r
5776 if (!sm2.debugMode) {\r
5777 // stop the SWF from making debug output calls to JS\r
5778 flash._disableDebug();\r
5779 }\r
5780\r
5781 sm2.enabled = true;\r
5782 debugTS('jstoflash', true);\r
5783\r
5784 if (!sm2.html5Only) {\r
5785 // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead\r
5786 // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/\r
5787 event.add(window, 'unload', doNothing);\r
5788 }\r
5789\r
5790 } catch(e) {\r
5791\r
5792 sm2._wD('js/flash exception: ' + e.toString());\r
5793 debugTS('jstoflash', false);\r
5794 catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true});\r
5795 // don't disable, for reboot()\r
5796 failSafely(true);\r
5797 initComplete();\r
5798\r
5799 return false;\r
5800\r
5801 }\r
5802\r
5803 initComplete();\r
5804\r
5805 // disconnect events\r
5806 cleanup();\r
5807\r
5808 return true;\r
5809\r
5810 };\r
5811\r
5812 domContentLoaded = function() {\r
5813\r
5814 if (didDCLoaded) {\r
5815 return false;\r
5816 }\r
5817\r
5818 didDCLoaded = true;\r
5819\r
5820 // assign top-level soundManager properties eg. soundManager.url\r
5821 setProperties();\r
5822\r
5823 initDebug();\r
5824\r
5825 /**\r
5826 * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1\r
5827 * Ditto for sm2-preferFlash, too.\r
5828 */\r
5829 // <d>\r
5830 (function(){\r
5831\r
5832 var a = 'sm2-usehtml5audio=',\r
5833 a2 = 'sm2-preferflash=',\r
5834 b = null,\r
5835 b2 = null,\r
5836 l = wl.toLowerCase();\r
5837\r
5838 if (l.indexOf(a) !== -1) {\r
5839 b = (l.charAt(l.indexOf(a)+a.length) === '1');\r
5840 if (hasConsole) {\r
5841 console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');\r
5842 }\r
5843 sm2.setup({\r
5844 'useHTML5Audio': b\r
5845 });\r
5846 }\r
5847\r
5848 if (l.indexOf(a2) !== -1) {\r
5849 b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');\r
5850 if (hasConsole) {\r
5851 console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');\r
5852 }\r
5853 sm2.setup({\r
5854 'preferFlash': b2\r
5855 });\r
5856 }\r
5857\r
5858 }());\r
5859 // </d>\r
5860\r
5861 if (!hasFlash && sm2.hasHTML5) {\r
5862 sm2._wD('SoundManager 2: No Flash detected' + (!sm2.useHTML5Audio ? ', enabling HTML5.' : '. Trying HTML5-only mode.'), 1);\r
5863 sm2.setup({\r
5864 'useHTML5Audio': true,\r
5865 // make sure we aren't preferring flash, either\r
5866 // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.\r
5867 'preferFlash': false\r
5868 });\r
5869 }\r
5870\r
5871 testHTML5();\r
5872\r
5873 if (!hasFlash && needsFlash) {\r
5874 messages.push(strings.needFlash);\r
5875 // TODO: Fatal here vs. timeout approach, etc.\r
5876 // hack: fail sooner.\r
5877 sm2.setup({\r
5878 'flashLoadTimeout': 1\r
5879 });\r
5880 }\r
5881\r
5882 if (doc.removeEventListener) {\r
5883 doc.removeEventListener('DOMContentLoaded', domContentLoaded, false);\r
5884 }\r
5885\r
5886 initMovie();\r
5887\r
5888 return true;\r
5889\r
5890 };\r
5891\r
5892 domContentLoadedIE = function() {\r
5893\r
5894 if (doc.readyState === 'complete') {\r
5895 domContentLoaded();\r
5896 doc.detachEvent('onreadystatechange', domContentLoadedIE);\r
5897 }\r
5898\r
5899 return true;\r
5900\r
5901 };\r
5902\r
5903 winOnLoad = function() {\r
5904\r
5905 // catch edge case of initComplete() firing after window.load()\r
5906 windowLoaded = true;\r
5907 event.remove(window, 'load', winOnLoad);\r
5908\r
5909 };\r
5910\r
5911 /**\r
5912 * miscellaneous run-time, pre-init stuff\r
5913 */\r
5914\r
5915 preInit = function() {\r
5916\r
5917 if (mobileHTML5) {\r
5918\r
5919 // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.\r
5920\r
5921 // <d>\r
5922 if (!sm2.setupOptions.useHTML5Audio || sm2.setupOptions.preferFlash) {\r
5923 // notify that defaults are being changed.\r
5924 messages.push(strings.mobileUA);\r
5925 }\r
5926 // </d>\r
5927\r
5928 sm2.setupOptions.useHTML5Audio = true;\r
5929 sm2.setupOptions.preferFlash = false;\r
5930\r
5931 if (is_iDevice || (isAndroid && !ua.match(/android\s2\.3/i))) {\r
5932 // iOS and Android devices tend to work better with a single audio instance, specifically for chained playback of sounds in sequence.\r
5933 // common use case: exiting sound onfinish() -> createSound() -> play()\r
5934 // <d>\r
5935 messages.push(strings.globalHTML5);\r
5936 // </d>\r
5937 if (is_iDevice) {\r
5938 sm2.ignoreFlash = true;\r
5939 }\r
5940 useGlobalHTML5Audio = true;\r
5941 }\r
5942\r
5943 }\r
5944\r
5945 };\r
5946\r
5947 preInit();\r
5948\r
5949 // sniff up-front\r
5950 detectFlash();\r
5951\r
5952 // focus and window load, init (primarily flash-driven)\r
5953 event.add(window, 'focus', handleFocus);\r
5954 event.add(window, 'load', delayWaitForEI);\r
5955 event.add(window, 'load', winOnLoad);\r
5956\r
5957 if (doc.addEventListener) {\r
5958\r
5959 doc.addEventListener('DOMContentLoaded', domContentLoaded, false);\r
5960\r
5961 } else if (doc.attachEvent) {\r
5962\r
5963 doc.attachEvent('onreadystatechange', domContentLoadedIE);\r
5964\r
5965 } else {\r
5966\r
5967 // no add/attachevent support - safe to assume no JS -> Flash either\r
5968 debugTS('onload', false);\r
5969 catchError({type:'NO_DOM2_EVENTS', fatal:true});\r
5970\r
5971 }\r
127631e0 5972\r
5ad04c7b 5973} // SoundManager()\r
127631e0 5974\r
45b0fa89
CP
5975// SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading\r
5976\r
5977if (window.SM2_DEFER === undefined || !SM2_DEFER) {\r
86f67adc 5978 soundManager = new SoundManager();\r
45b0fa89
CP
5979}\r
5980\r
5981/**\r
5982 * SoundManager public interfaces\r
5983 * ------------------------------\r
5984 */\r
5985\r
5986window.SoundManager = SoundManager; // constructor\r
5987window.soundManager = soundManager; // public API, flash callbacks etc.\r
5988\r
5989}(window));\r