]>
Commit | Line | Data |
---|---|---|
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 | 37 | var 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 | |
49 | function 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 <a> object (or similar object literal) based on audio support.\r | |
1045 | *\r | |
1046 | * @param {object} oLink an HTML DOM <a> 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 | |
5977 | if (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 | |
5986 | window.SoundManager = SoundManager; // constructor\r | |
5987 | window.soundManager = soundManager; // public API, flash callbacks etc.\r | |
5988 | \r | |
5989 | }(window));\r |