masslugi

Script JS Sound manager2 Audio Player

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