SoundJS v1.0.0 API Documentation : soundjs/Sound.js

API Documentation for: 1.0.0
Show:

File:Sound.js

  1. /*
  2. * Sound
  3. * Visit http://createjs.com/ for documentation, updates and examples.
  4. *
  5. *
  6. * Copyright (c) 2012 gskinner.com, inc.
  7. *
  8. * Permission is hereby granted, free of charge, to any person
  9. * obtaining a copy of this software and associated documentation
  10. * files (the "Software"), to deal in the Software without
  11. * restriction, including without limitation the rights to use,
  12. * copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the
  14. * Software is furnished to do so, subject to the following
  15. * conditions:
  16. *
  17. * The above copyright notice and this permission notice shall be
  18. * included in all copies or substantial portions of the Software.
  19. *
  20. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  21. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  22. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  23. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  24. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  25. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  26. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  27. * OTHER DEALINGS IN THE SOFTWARE.
  28. */
  29.  
  30. // namespace:
  31. this.createjs = this.createjs || {};
  32.  
  33. /**
  34. * The SoundJS library manages the playback of audio on the web. It works via plugins which abstract the actual audio
  35. * implementation, so playback is possible on any platform without specific knowledge of what mechanisms are necessary
  36. * to play sounds.
  37. *
  38. * To use SoundJS, use the public API on the {{#crossLink "Sound"}}{{/crossLink}} class. This API is for:
  39. * <ul>
  40. * <li>Installing audio playback Plugins</li>
  41. * <li>Registering (and preloading) sounds</li>
  42. * <li>Creating and playing sounds</li>
  43. * <li>Master volume, mute, and stop controls for all sounds at once</li>
  44. * </ul>
  45. *
  46. * <b>Controlling Sounds</b><br />
  47. * Playing sounds creates {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} instances, which can be controlled
  48. * individually.
  49. * <ul>
  50. * <li>Pause, resume, seek, and stop sounds</li>
  51. * <li>Control a sound's volume, mute, and pan</li>
  52. * <li>Listen for events on sound instances to get notified when they finish, loop, or fail</li>
  53. * </ul>
  54. *
  55. * <h4>Example</h4>
  56. *
  57. * createjs.Sound.alternateExtensions = ["mp3"];
  58. * createjs.Sound.on("fileload", this.loadHandler, this);
  59. * createjs.Sound.registerSound("path/to/mySound.ogg", "sound");
  60. * function loadHandler(event) {
  61. * // This is fired for each sound that is registered.
  62. * var instance = createjs.Sound.play("sound"); // play using id. Could also use full sourcepath or event.src.
  63. * instance.on("complete", this.handleComplete, this);
  64. * instance.volume = 0.5;
  65. * }
  66. *
  67. * <h4>Browser Support</h4>
  68. * Audio will work in browsers which support Web Audio (<a href="http://caniuse.com/audio-api" target="_blank">http://caniuse.com/audio-api</a>)
  69. * or HTMLAudioElement (<a href="http://caniuse.com/audio" target="_blank">http://caniuse.com/audio</a>).
  70. * A Flash fallback can be used for any browser that supports the Flash player, and the Cordova plugin can be used in
  71. * any webview that supports <a href="http://plugins.cordova.io/#/package/org.apache.cordova.media" target="_blank">Cordova.Media</a>.
  72. * IE8 and earlier are not supported, even with the Flash fallback. To support earlier browsers, you can use an older
  73. * version of SoundJS (version 0.5.2 and earlier).
  74. *
  75. * @module SoundJS
  76. * @main SoundJS
  77. */
  78.  
  79. (function () {
  80. "use strict";
  81.  
  82. /**
  83. * The Sound class is the public API for creating sounds, controlling the overall sound levels, and managing plugins.
  84. * All Sound APIs on this class are static.
  85. *
  86. * <b>Registering and Preloading</b><br />
  87. * Before you can play a sound, it <b>must</b> be registered. You can do this with {{#crossLink "Sound/registerSound"}}{{/crossLink}},
  88. * or register multiple sounds using {{#crossLink "Sound/registerSounds"}}{{/crossLink}}. If you don't register a
  89. * sound prior to attempting to play it using {{#crossLink "Sound/play"}}{{/crossLink}} or create it using {{#crossLink "Sound/createInstance"}}{{/crossLink}},
  90. * the sound source will be automatically registered but playback will fail as the source will not be ready. If you use
  91. * <a href="http://preloadjs.com" target="_blank">PreloadJS</a>, registration is handled for you when the sound is
  92. * preloaded. It is recommended to preload sounds either internally using the register functions or externally using
  93. * PreloadJS so they are ready when you want to use them.
  94. *
  95. * <b>Playback</b><br />
  96. * To play a sound once it's been registered and preloaded, use the {{#crossLink "Sound/play"}}{{/crossLink}} method.
  97. * This method returns a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} which can be paused, resumed, muted, etc.
  98. * Please see the {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} documentation for more on the instance control APIs.
  99. *
  100. * <b>Plugins</b><br />
  101. * By default, the {{#crossLink "WebAudioPlugin"}}{{/crossLink}} or the {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}
  102. * are used (when available), although developers can change plugin priority or add new plugins (such as the
  103. * provided {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}). Please see the {{#crossLink "Sound"}}{{/crossLink}} API
  104. * methods for more on the playback and plugin APIs. To install plugins, or specify a different plugin order, see
  105. * {{#crossLink "Sound/installPlugins"}}{{/crossLink}}.
  106. *
  107. * <h4>Example</h4>
  108. *
  109. * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio";
  110. * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashAudioPlugin]);
  111. * createjs.Sound.alternateExtensions = ["mp3"];
  112. * createjs.Sound.on("fileload", this.loadHandler, this);
  113. * createjs.Sound.registerSound("path/to/mySound.ogg", "sound");
  114. * function loadHandler(event) {
  115. * // This is fired for each sound that is registered.
  116. * var instance = createjs.Sound.play("sound"); // play using id. Could also use full source path or event.src.
  117. * instance.on("complete", this.handleComplete, this);
  118. * instance.volume = 0.5;
  119. * }
  120. *
  121. * The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument
  122. * of {{#crossLink "Sound/registerSound"}}{{/crossLink}}. Note that if not specified, the active plugin will apply
  123. * a default limit. Currently HTMLAudioPlugin sets a default limit of 2, while WebAudioPlugin and FlashAudioPlugin set a
  124. * default limit of 100.
  125. *
  126. * createjs.Sound.registerSound("sound.mp3", "soundId", 4);
  127. *
  128. * Sound can be used as a plugin with PreloadJS to help preload audio properly. Audio preloaded with PreloadJS is
  129. * automatically registered with the Sound class. When audio is not preloaded, Sound will do an automatic internal
  130. * load. As a result, it may fail to play the first time play is called if the audio is not finished loading. Use
  131. * the {{#crossLink "Sound/fileload:event"}}{{/crossLink}} event to determine when a sound has finished internally
  132. * preloading. It is recommended that all audio is preloaded before it is played.
  133. *
  134. * var queue = new createjs.LoadQueue();
  135. * queue.installPlugin(createjs.Sound);
  136. *
  137. * <b>Audio Sprites</b><br />
  138. * SoundJS has added support for {{#crossLink "AudioSprite"}}{{/crossLink}}, available as of version 0.6.0.
  139. * For those unfamiliar with audio sprites, they are much like CSS sprites or sprite sheets: multiple audio assets
  140. * grouped into a single file.
  141. *
  142. * <h4>Example</h4>
  143. *
  144. * var assetsPath = "./assets/";
  145. * var sounds = [{
  146. * src:"MyAudioSprite.ogg", data: {
  147. * audioSprite: [
  148. * {id:"sound1", startTime:0, duration:500},
  149. * {id:"sound2", startTime:1000, duration:400},
  150. * {id:"sound3", startTime:1700, duration: 1000}
  151. * ]}
  152. * }
  153. * ];
  154. * createjs.Sound.alternateExtensions = ["mp3"];
  155. * createjs.Sound.on("fileload", loadSound);
  156. * createjs.Sound.registerSounds(sounds, assetsPath);
  157. * // after load is complete
  158. * createjs.Sound.play("sound2");
  159. *
  160. * <b>Mobile Playback</b><br />
  161. * Devices running iOS require the WebAudio context to be "unlocked" by playing at least one sound inside of a user-
  162. * initiated event (such as touch/click). Earlier versions of SoundJS included a "MobileSafe" sample, but this is no
  163. * longer necessary as of SoundJS 0.6.2.
  164. * <ul>
  165. * <li>
  166. * In SoundJS 0.4.1 and above, you can either initialize plugins or use the {{#crossLink "WebAudioPlugin/playEmptySound"}}{{/crossLink}}
  167. * method in the call stack of a user input event to manually unlock the audio context.
  168. * </li>
  169. * <li>
  170. * In SoundJS 0.6.2 and above, SoundJS will automatically listen for the first document-level "mousedown"
  171. * and "touchend" event, and unlock WebAudio. This will continue to check these events until the WebAudio
  172. * context becomes "unlocked" (changes from "suspended" to "running")
  173. * </li>
  174. * <li>
  175. * Both the "mousedown" and "touchend" events can be used to unlock audio in iOS9+, the "touchstart" event
  176. * will work in iOS8 and below. The "touchend" event will only work in iOS9 when the gesture is interpreted
  177. * as a "click", so if the user long-presses the button, it will no longer work.
  178. * </li>
  179. * <li>
  180. * When using the <a href="http://www.createjs.com/docs/easeljs/classes/Touch.html">EaselJS Touch class</a>,
  181. * the "mousedown" event will not fire when a canvas is clicked, since MouseEvents are prevented, to ensure
  182. * only touch events fire. To get around this, you can either rely on "touchend", or:
  183. * <ol>
  184. * <li>Set the `allowDefault` property on the Touch class constructor to `true` (defaults to `false`).</li>
  185. * <li>Set the `preventSelection` property on the EaselJS `Stage` to `false`.</li>
  186. * </ol>
  187. * These settings may change how your application behaves, and are not recommended.
  188. * </li>
  189. * </ul>
  190. *
  191. * <b>Loading Alternate Paths and Extension-less Files</b><br />
  192. * SoundJS supports loading alternate paths and extension-less files by passing an object instead of a string for
  193. * the `src` property, which is a hash using the format `{extension:"path", extension2:"path2"}`. These labels are
  194. * how SoundJS determines if the browser will support the sound. This also enables multiple formats to live in
  195. * different folders, or on CDNs, which often has completely different filenames for each file.
  196. *
  197. * Priority is determined by the property order (first property is tried first). This is supported by both internal loading
  198. * and loading with PreloadJS.
  199. *
  200. * <em>Note: an id is required for playback.</em>
  201. *
  202. * <h4>Example</h4>
  203. *
  204. * var sounds = {path:"./audioPath/",
  205. * manifest: [
  206. * {id: "cool", src: {mp3:"mp3/awesome.mp3", ogg:"noExtensionOggFile"}}
  207. * ]};
  208. *
  209. * createjs.Sound.alternateExtensions = ["mp3"];
  210. * createjs.Sound.addEventListener("fileload", handleLoad);
  211. * createjs.Sound.registerSounds(sounds);
  212. *
  213. * <h3>Known Browser and OS issues</h3>
  214. * <b>IE 9 HTML Audio limitations</b><br />
  215. * <ul><li>There is a delay in applying volume changes to tags that occurs once playback is started. So if you have
  216. * muted all sounds, they will all play during this delay until the mute applies internally. This happens regardless of
  217. * when or how you apply the volume change, as the tag seems to need to play to apply it.</li>
  218. * <li>MP3 encoding will not always work for audio tags, particularly in Internet Explorer. We've found default
  219. * encoding with 64kbps works.</li>
  220. * <li>Occasionally very short samples will get cut off.</li>
  221. * <li>There is a limit to how many audio tags you can load and play at once, which appears to be determined by
  222. * hardware and browser settings. See {{#crossLink "HTMLAudioPlugin.MAX_INSTANCES"}}{{/crossLink}} for a safe
  223. * estimate.</li></ul>
  224. *
  225. * <b>Firefox 25 Web Audio limitations</b>
  226. * <ul><li>mp3 audio files do not load properly on all windows machines, reported
  227. * <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target="_blank">here</a>. </br>
  228. * For this reason it is recommended to pass another FF supported type (ie ogg) first until this bug is resolved, if
  229. * possible.</li></ul>
  230.  
  231. * <b>Safari limitations</b><br />
  232. * <ul><li>Safari requires Quicktime to be installed for audio playback.</li></ul>
  233. *
  234. * <b>iOS 6 Web Audio limitations</b><br />
  235. * <ul><li>Sound is initially locked, and must be unlocked via a user-initiated event. Please see the section on
  236. * Mobile Playback above.</li>
  237. * <li>A bug exists that will distort un-cached web audio when a video element is present in the DOM that has audio
  238. * at a different sampleRate.</li>
  239. * </ul>
  240. *
  241. * <b>Android HTML Audio limitations</b><br />
  242. * <ul><li>We have no control over audio volume. Only the user can set volume on their device.</li>
  243. * <li>We can only play audio inside a user event (touch/click). This currently means you cannot loop sound or use
  244. * a delay.</li></ul>
  245. *
  246. * <b>Web Audio and PreloadJS</b><br />
  247. * <ul><li>Web Audio must be loaded through XHR, therefore when used with PreloadJS, tag loading is not possible.
  248. * This means that tag loading can not be used to avoid cross domain issues.</li><ul>
  249. *
  250. * @class Sound
  251. * @static
  252. * @uses EventDispatcher
  253. */
  254. function Sound() {
  255. throw "Sound cannot be instantiated";
  256. }
  257.  
  258. var s = Sound;
  259.  
  260.  
  261. // Static Properties
  262. /**
  263. * The interrupt value to interrupt any currently playing instance with the same source, if the maximum number of
  264. * instances of the sound are already playing.
  265. * @property INTERRUPT_ANY
  266. * @type {String}
  267. * @default any
  268. * @static
  269. */
  270. s.INTERRUPT_ANY = "any";
  271.  
  272. /**
  273. * The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the
  274. * least distance in the audio track, if the maximum number of instances of the sound are already playing.
  275. * @property INTERRUPT_EARLY
  276. * @type {String}
  277. * @default early
  278. * @static
  279. */
  280. s.INTERRUPT_EARLY = "early";
  281.  
  282. /**
  283. * The interrupt value to interrupt the currently playing instance with the same source that progressed the most
  284. * distance in the audio track, if the maximum number of instances of the sound are already playing.
  285. * @property INTERRUPT_LATE
  286. * @type {String}
  287. * @default late
  288. * @static
  289. */
  290. s.INTERRUPT_LATE = "late";
  291.  
  292. /**
  293. * The interrupt value to not interrupt any currently playing instances with the same source, if the maximum number of
  294. * instances of the sound are already playing.
  295. * @property INTERRUPT_NONE
  296. * @type {String}
  297. * @default none
  298. * @static
  299. */
  300. s.INTERRUPT_NONE = "none";
  301.  
  302. /**
  303. * Defines the playState of an instance that is still initializing.
  304. * @property PLAY_INITED
  305. * @type {String}
  306. * @default playInited
  307. * @static
  308. */
  309. s.PLAY_INITED = "playInited";
  310.  
  311. /**
  312. * Defines the playState of an instance that is currently playing or paused.
  313. * @property PLAY_SUCCEEDED
  314. * @type {String}
  315. * @default playSucceeded
  316. * @static
  317. */
  318. s.PLAY_SUCCEEDED = "playSucceeded";
  319.  
  320. /**
  321. * Defines the playState of an instance that was interrupted by another instance.
  322. * @property PLAY_INTERRUPTED
  323. * @type {String}
  324. * @default playInterrupted
  325. * @static
  326. */
  327. s.PLAY_INTERRUPTED = "playInterrupted";
  328.  
  329. /**
  330. * Defines the playState of an instance that completed playback.
  331. * @property PLAY_FINISHED
  332. * @type {String}
  333. * @default playFinished
  334. * @static
  335. */
  336. s.PLAY_FINISHED = "playFinished";
  337.  
  338. /**
  339. * Defines the playState of an instance that failed to play. This is usually caused by a lack of available channels
  340. * when the interrupt mode was "INTERRUPT_NONE", the playback stalled, or the sound could not be found.
  341. * @property PLAY_FAILED
  342. * @type {String}
  343. * @default playFailed
  344. * @static
  345. */
  346. s.PLAY_FAILED = "playFailed";
  347.  
  348. /**
  349. * A list of the default supported extensions that Sound will <i>try</i> to play. Plugins will check if the browser
  350. * can play these types, so modifying this list before a plugin is initialized will allow the plugins to try to
  351. * support additional media types.
  352. *
  353. * NOTE this does not currently work for {{#crossLink "FlashAudioPlugin"}}{{/crossLink}}.
  354. *
  355. * More details on file formats can be found at <a href="http://en.wikipedia.org/wiki/Audio_file_format" target="_blank">http://en.wikipedia.org/wiki/Audio_file_format</a>.<br />
  356. * A very detailed list of file formats can be found at <a href="http://www.fileinfo.com/filetypes/audio" target="_blank">http://www.fileinfo.com/filetypes/audio</a>.
  357. * @property SUPPORTED_EXTENSIONS
  358. * @type {Array[String]}
  359. * @default ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"]
  360. * @since 0.4.0
  361. * @static
  362. */
  363. s.SUPPORTED_EXTENSIONS = ["mp3", "ogg", "opus", "mpeg", "wav", "m4a", "mp4", "aiff", "wma", "mid"];
  364.  
  365. /**
  366. * Some extensions use another type of extension support to play (one of them is a codex). This allows you to map
  367. * that support so plugins can accurately determine if an extension is supported. Adding to this list can help
  368. * plugins determine more accurately if an extension is supported.
  369. *
  370. * A useful list of extensions for each format can be found at <a href="http://html5doctor.com/html5-audio-the-state-of-play/" target="_blank">http://html5doctor.com/html5-audio-the-state-of-play/</a>.
  371. * @property EXTENSION_MAP
  372. * @type {Object}
  373. * @since 0.4.0
  374. * @default {m4a:"mp4"}
  375. * @static
  376. */
  377. s.EXTENSION_MAP = {
  378. m4a:"mp4"
  379. };
  380.  
  381. /**
  382. * The RegExp pattern used to parse file URIs. This supports simple file names, as well as full domain URIs with
  383. * query strings. The resulting match is: protocol:$1 domain:$2 path:$3 file:$4 extension:$5 query:$6.
  384. * @property FILE_PATTERN
  385. * @type {RegExp}
  386. * @static
  387. * @private
  388. */
  389. s.FILE_PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/;
  390.  
  391.  
  392. // Class Public properties
  393. /**
  394. * Determines the default behavior for interrupting other currently playing instances with the same source, if the
  395. * maximum number of instances of the sound are already playing. Currently the default is {{#crossLink "Sound/INTERRUPT_NONE:property"}}{{/crossLink}}
  396. * but this can be set and will change playback behavior accordingly. This is only used when {{#crossLink "Sound/play"}}{{/crossLink}}
  397. * is called without passing a value for interrupt.
  398. * @property defaultInterruptBehavior
  399. * @type {String}
  400. * @default Sound.INTERRUPT_NONE, or "none"
  401. * @static
  402. * @since 0.4.0
  403. */
  404. s.defaultInterruptBehavior = s.INTERRUPT_NONE; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense.
  405.  
  406. /**
  407. * An array of extensions to attempt to use when loading sound, if the default is unsupported by the active plugin.
  408. * These are applied in order, so if you try to Load Thunder.ogg in a browser that does not support ogg, and your
  409. * extensions array is ["mp3", "m4a", "wav"] it will check mp3 support, then m4a, then wav. The audio files need
  410. * to exist in the same location, as only the extension is altered.
  411. *
  412. * Note that regardless of which file is loaded, you can call {{#crossLink "Sound/createInstance"}}{{/crossLink}}
  413. * and {{#crossLink "Sound/play"}}{{/crossLink}} using the same id or full source path passed for loading.
  414. *
  415. * <h4>Example</h4>
  416. *
  417. * var sounds = [
  418. * {src:"myPath/mySound.ogg", id:"example"},
  419. * ];
  420. * createjs.Sound.alternateExtensions = ["mp3"]; // now if ogg is not supported, SoundJS will try asset0.mp3
  421. * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads
  422. * createjs.Sound.registerSounds(sounds, assetPath);
  423. * // ...
  424. * createjs.Sound.play("myPath/mySound.ogg"); // works regardless of what extension is supported. Note calling with ID is a better approach
  425. *
  426. * @property alternateExtensions
  427. * @type {Array}
  428. * @since 0.5.2
  429. * @static
  430. */
  431. s.alternateExtensions = [];
  432.  
  433. /**
  434. * The currently active plugin. If this is null, then no plugin could be initialized. If no plugin was specified,
  435. * Sound attempts to apply the default plugins: {{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by
  436. * {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
  437. * @property activePlugin
  438. * @type {Object}
  439. * @static
  440. */
  441. s.activePlugin = null;
  442.  
  443.  
  444. // class getter / setter properties
  445.  
  446. /**
  447. * Set the master volume of Sound. The master volume is multiplied against each sound's individual volume. For
  448. * example, if master volume is 0.5 and a sound's volume is 0.5, the resulting volume is 0.25. To set individual
  449. * sound volume, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/volume:property"}}{{/crossLink}}
  450. * instead.
  451. *
  452. * <h4>Example</h4>
  453. *
  454. * createjs.Sound.volume = 0.5;
  455. *
  456. * @property volume
  457. * @type {Number}
  458. * @default 1
  459. * @static
  460. * @since 0.6.1
  461. */
  462.  
  463. /**
  464. * The internal volume level. Use {{#crossLink "Sound/volume:property"}}{{/crossLink}} to adjust the master volume.
  465. * @property _masterVolume
  466. * @type {number}
  467. * @default 1
  468. * @private
  469. */
  470. s._masterVolume = 1;
  471.  
  472. /**
  473. * Use the {{#crossLink "Sound/volume:property"}}{{/crossLink}} property instead.
  474. * @method _getMasterVolume
  475. * @private
  476. * @static
  477. * @return {Number}
  478. **/
  479. s._getMasterVolume = function() {
  480. return this._masterVolume;
  481. };
  482.  
  483. /**
  484. * Use the {{#crossLink "Sound/volume:property"}}{{/crossLink}} property instead.
  485. * @method getMasterVolume
  486. * @deprecated
  487. */
  488. // Sound.getMasterVolume is @deprecated. Remove for 1.1+
  489. s.getVolume = createjs.deprecate(s._getMasterVolume, "Sound.getVolume");
  490. /**
  491. * Use the {{#crossLink "Sound/volume:property"}}{{/crossLink}} property instead.
  492. * @method _setMasterVolume
  493. * @static
  494. * @private
  495. **/
  496. s._setMasterVolume = function(value) {
  497. if (Number(value) == null) { return; }
  498. value = Math.max(0, Math.min(1, value));
  499. s._masterVolume = value;
  500. if (!this.activePlugin || !this.activePlugin.setVolume || !this.activePlugin.setVolume(value)) {
  501. var instances = this._instances;
  502. for (var i = 0, l = instances.length; i < l; i++) {
  503. instances[i].setMasterVolume(value);
  504. }
  505. }
  506. };
  507.  
  508. /**
  509. * Use the {{#crossLink "Sound/volume:property"}}{{/crossLink}} property instead.
  510. * @method setVolume
  511. * @deprecated
  512. */
  513. // Sound.setVolume is @deprecated. Remove for 1.1+
  514. s.setVolume = createjs.deprecate(s._setMasterVolume, "Sound.setVolume");
  515.  
  516. /**
  517. * Mute/Unmute all audio. Note that muted audio still plays at 0 volume. This global mute value is maintained
  518. * separately and when set will override, but not change the mute property of individual instances. To mute an individual
  519. * instance, use AbstractSoundInstance {{#crossLink "AbstractSoundInstance/muted:property"}}{{/crossLink}} instead.
  520. *
  521. * <h4>Example</h4>
  522. *
  523. * createjs.Sound.muted = true;
  524. *
  525. *
  526. * @property muted
  527. * @type {Boolean}
  528. * @default false
  529. * @static
  530. * @since 0.6.1
  531. */
  532. s._masterMute = false;
  533.  
  534. /**
  535. * Use the {{#crossLink "Sound/muted:property"}}{{/crossLink}} property instead.
  536. * @method _getMute
  537. * @returns {Boolean}
  538. * @static
  539. * @private
  540. */
  541. s._getMute = function () {
  542. return this._masterMute;
  543. };
  544.  
  545. /**
  546. * Use the {{#crossLink "Sound/muted:property"}}{{/crossLink}} property instead.
  547. * @method getMute
  548. * @deprecated
  549. */
  550. // Sound.getMute is @deprecated. Remove for 1.1+
  551. s.getMute = createjs.deprecate(s._getMute, "Sound.getMute");
  552.  
  553. /**
  554. * Use the {{#crossLink "Sound/muted:property"}}{{/crossLink}} property instead.
  555. * @method _setMute
  556. * @param {Boolean} value The muted value
  557. * @static
  558. * @private
  559. */
  560. s._setMute = function (value) {
  561. if (value == null) { return; }
  562. this._masterMute = value;
  563. if (!this.activePlugin || !this.activePlugin.setMute || !this.activePlugin.setMute(value)) {
  564. var instances = this._instances;
  565. for (var i = 0, l = instances.length; i < l; i++) {
  566. instances[i].setMasterMute(value);
  567. }
  568. }
  569. };
  570.  
  571. /**
  572. * Use the {{#crossLink "Sound/muted:property"}}{{/crossLink}} property instead.
  573. * @method setMute
  574. * @deprecated
  575. */
  576. // Sound.setMute is @deprecated. Remove for 1.1+
  577. s.setMute = createjs.deprecate(s._setMute, "Sound.setMute");
  578.  
  579. /**
  580. * Get the active plugins capabilities, which help determine if a plugin can be used in the current environment,
  581. * or if the plugin supports a specific feature. Capabilities include:
  582. * <ul>
  583. * <li><b>panning:</b> If the plugin can pan audio from left to right</li>
  584. * <li><b>volume;</b> If the plugin can control audio volume.</li>
  585. * <li><b>tracks:</b> The maximum number of audio tracks that can be played back at a time. This will be -1
  586. * if there is no known limit.</li>
  587. * <br />An entry for each file type in {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}:
  588. * <li><b>mp3:</b> If MP3 audio is supported.</li>
  589. * <li><b>ogg:</b> If OGG audio is supported.</li>
  590. * <li><b>wav:</b> If WAV audio is supported.</li>
  591. * <li><b>mpeg:</b> If MPEG audio is supported.</li>
  592. * <li><b>m4a:</b> If M4A audio is supported.</li>
  593. * <li><b>mp4:</b> If MP4 audio is supported.</li>
  594. * <li><b>aiff:</b> If aiff audio is supported.</li>
  595. * <li><b>wma:</b> If wma audio is supported.</li>
  596. * <li><b>mid:</b> If mid audio is supported.</li>
  597. * </ul>
  598. *
  599. * You can get a specific capability of the active plugin using standard object notation
  600. *
  601. * <h4>Example</h4>
  602. *
  603. * var mp3 = createjs.Sound.capabilities.mp3;
  604. *
  605. * Note this property is read only.
  606. *
  607. * @property capabilities
  608. * @type {Object}
  609. * @static
  610. * @readOnly
  611. * @since 0.6.1
  612. */
  613.  
  614. /**
  615. * Use the {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} property instead.
  616. * @returns {null}
  617. * @private
  618. */
  619. s._getCapabilities = function() {
  620. if (s.activePlugin == null) { return null; }
  621. return s.activePlugin._capabilities;
  622. };
  623.  
  624. /**
  625. * Use the {{#crossLink "Sound/capabilities:property"}}{{/crossLink}} property instead.
  626. * @method getCapabilities
  627. * @deprecated
  628. */
  629. // Sound.getCapabilities is @deprecated. Remove for 1.1+
  630. s.getCapabilities = createjs.deprecate(s._getCapabilities, "Sound.getCapabilities");
  631.  
  632. Object.defineProperties(s, {
  633. volume: { get: s._getMasterVolume, set: s._setMasterVolume },
  634. muted: { get: s._getMute, set: s._setMute },
  635. capabilities: { get: s._getCapabilities }
  636. });
  637.  
  638.  
  639. // Class Private properties
  640. /**
  641. * Determines if the plugins have been registered. If false, the first call to {{#crossLink "play"}}{{/crossLink}} will instantiate the default
  642. * plugins ({{#crossLink "WebAudioPlugin"}}{{/crossLink}}, followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}).
  643. * If plugins have been registered, but none are applicable, then sound playback will fail.
  644. * @property _pluginsRegistered
  645. * @type {Boolean}
  646. * @default false
  647. * @static
  648. * @private
  649. */
  650. s._pluginsRegistered = false;
  651.  
  652. /**
  653. * Used internally to assign unique IDs to each AbstractSoundInstance.
  654. * @property _lastID
  655. * @type {Number}
  656. * @static
  657. * @private
  658. */
  659. s._lastID = 0;
  660.  
  661. /**
  662. * An array containing all currently playing instances. This allows Sound to control the volume, mute, and playback of
  663. * all instances when using static APIs like {{#crossLink "Sound/stop"}}{{/crossLink}} and {{#crossLink "Sound/volume:property"}}{{/crossLink}}.
  664. * When an instance has finished playback, it gets removed via the {{#crossLink "Sound/finishedPlaying"}}{{/crossLink}}
  665. * method. If the user replays an instance, it gets added back in via the {{#crossLink "Sound/_beginPlaying"}}{{/crossLink}}
  666. * method.
  667. * @property _instances
  668. * @type {Array}
  669. * @private
  670. * @static
  671. */
  672. s._instances = [];
  673.  
  674. /**
  675. * An object hash storing objects with sound sources, startTime, and duration via there corresponding ID.
  676. * @property _idHash
  677. * @type {Object}
  678. * @private
  679. * @static
  680. */
  681. s._idHash = {};
  682.  
  683. /**
  684. * An object hash that stores preloading sound sources via the parsed source that is passed to the plugin. Contains the
  685. * source, id, and data that was passed in by the user. Parsed sources can contain multiple instances of source, id,
  686. * and data.
  687. * @property _preloadHash
  688. * @type {Object}
  689. * @private
  690. * @static
  691. */
  692. s._preloadHash = {};
  693.  
  694. /**
  695. * An object hash storing {{#crossLink "PlayPropsConfig"}}{{/crossLink}} via the parsed source that is passed as defaultPlayProps in
  696. * {{#crossLink "Sound/registerSound"}}{{/crossLink}} and {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
  697. * @property _defaultPlayPropsHash
  698. * @type {Object}
  699. * @private
  700. * @static
  701. * @since 0.6.1
  702. */
  703. s._defaultPlayPropsHash = {};
  704.  
  705.  
  706. // EventDispatcher methods:
  707. s.addEventListener = null;
  708. s.removeEventListener = null;
  709. s.removeAllEventListeners = null;
  710. s.dispatchEvent = null;
  711. s.hasEventListener = null;
  712. s._listeners = null;
  713.  
  714. createjs.EventDispatcher.initialize(s); // inject EventDispatcher methods.
  715.  
  716.  
  717. // Events
  718. /**
  719. * This event is fired when a file finishes loading internally. This event is fired for each loaded sound,
  720. * so any handler methods should look up the <code>event.src</code> to handle a particular sound.
  721. * @event fileload
  722. * @param {Object} target The object that dispatched the event.
  723. * @param {String} type The event type.
  724. * @param {String} src The source of the sound that was loaded.
  725. * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null.
  726. * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined.
  727. * @since 0.4.1
  728. */
  729.  
  730. /**
  731. * This event is fired when a file fails loading internally. This event is fired for each loaded sound,
  732. * so any handler methods should look up the <code>event.src</code> to handle a particular sound.
  733. * @event fileerror
  734. * @param {Object} target The object that dispatched the event.
  735. * @param {String} type The event type.
  736. * @param {String} src The source of the sound that was loaded.
  737. * @param {String} [id] The id passed in when the sound was registered. If one was not provided, it will be null.
  738. * @param {Number|Object} [data] Any additional data associated with the item. If not provided, it will be undefined.
  739. * @since 0.6.0
  740. */
  741.  
  742.  
  743. // Class Public Methods
  744. /**
  745. * Get the preload rules to allow Sound to be used as a plugin by <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
  746. * Any load calls that have the matching type or extension will fire the callback method, and use the resulting
  747. * object, which is potentially modified by Sound. This helps when determining the correct path, as well as
  748. * registering the audio instance(s) with Sound. This method should not be called, except by PreloadJS.
  749. * @method getPreloadHandlers
  750. * @return {Object} An object containing:
  751. * <ul><li>callback: A preload callback that is fired when a file is added to PreloadJS, which provides
  752. * Sound a mechanism to modify the load parameters, select the correct file format, register the sound, etc.</li>
  753. * <li>types: A list of file types that are supported by Sound (currently supports "sound").</li>
  754. * <li>extensions: A list of file extensions that are supported by Sound (see {{#crossLink "Sound/SUPPORTED_EXTENSIONS:property"}}{{/crossLink}}).</li></ul>
  755. * @static
  756. * @private
  757. */
  758. s.getPreloadHandlers = function () {
  759. return {
  760. callback:createjs.proxy(s.initLoad, s),
  761. types:["sound"],
  762. extensions:s.SUPPORTED_EXTENSIONS
  763. };
  764. };
  765.  
  766. /**
  767. * Used to dispatch fileload events from internal loading.
  768. * @method _handleLoadComplete
  769. * @param event A loader event.
  770. * @private
  771. * @static
  772. * @since 0.6.0
  773. */
  774. s._handleLoadComplete = function(event) {
  775. var src = event.target.getItem().src;
  776. if (!s._preloadHash[src]) {return;}
  777.  
  778. for (var i = 0, l = s._preloadHash[src].length; i < l; i++) {
  779. var item = s._preloadHash[src][i];
  780. s._preloadHash[src][i] = true;
  781.  
  782. if (!s.hasEventListener("fileload")) { continue; }
  783.  
  784. var event = new createjs.Event("fileload");
  785. event.src = item.src;
  786. event.id = item.id;
  787. event.data = item.data;
  788. event.sprite = item.sprite;
  789.  
  790. s.dispatchEvent(event);
  791. }
  792. };
  793.  
  794. /**
  795. * Used to dispatch error events from internal preloading.
  796. * @param event
  797. * @private
  798. * @since 0.6.0
  799. * @static
  800. */
  801. s._handleLoadError = function(event) {
  802. var src = event.target.getItem().src;
  803. if (!s._preloadHash[src]) {return;}
  804.  
  805. for (var i = 0, l = s._preloadHash[src].length; i < l; i++) {
  806. var item = s._preloadHash[src][i];
  807. s._preloadHash[src][i] = false;
  808.  
  809. if (!s.hasEventListener("fileerror")) { continue; }
  810.  
  811. var event = new createjs.Event("fileerror");
  812. event.src = item.src;
  813. event.id = item.id;
  814. event.data = item.data;
  815. event.sprite = item.sprite;
  816.  
  817. s.dispatchEvent(event);
  818. }
  819. };
  820.  
  821. /**
  822. * Used by {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} to register a Sound plugin.
  823. *
  824. * @method _registerPlugin
  825. * @param {Object} plugin The plugin class to install.
  826. * @return {Boolean} Whether the plugin was successfully initialized.
  827. * @static
  828. * @private
  829. */
  830. s._registerPlugin = function (plugin) {
  831. // Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance
  832. if (plugin.isSupported()) {
  833. s.activePlugin = new plugin();
  834. return true;
  835. }
  836. return false;
  837. };
  838.  
  839. /**
  840. * Register a list of Sound plugins, in order of precedence. To register a single plugin, pass a single element in the array.
  841. *
  842. * <h4>Example</h4>
  843. *
  844. * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/";
  845. * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]);
  846. *
  847. * @method registerPlugins
  848. * @param {Array} plugins An array of plugins classes to install.
  849. * @return {Boolean} Whether a plugin was successfully initialized.
  850. * @static
  851. */
  852. s.registerPlugins = function (plugins) {
  853. s._pluginsRegistered = true;
  854. for (var i = 0, l = plugins.length; i < l; i++) {
  855. if (s._registerPlugin(plugins[i])) {
  856. return true;
  857. }
  858. }
  859. return false;
  860. };
  861.  
  862. /**
  863. * Initialize the default plugins. This method is automatically called when any audio is played or registered before
  864. * the user has manually registered plugins, and enables Sound to work without manual plugin setup. Currently, the
  865. * default plugins are {{#crossLink "WebAudioPlugin"}}{{/crossLink}} followed by {{#crossLink "HTMLAudioPlugin"}}{{/crossLink}}.
  866. *
  867. * <h4>Example</h4>
  868. *
  869. * if (!createjs.initializeDefaultPlugins()) { return; }
  870. *
  871. * @method initializeDefaultPlugins
  872. * @returns {Boolean} True if a plugin was initialized, false otherwise.
  873. * @since 0.4.0
  874. * @static
  875. */
  876. s.initializeDefaultPlugins = function () {
  877. if (s.activePlugin != null) {return true;}
  878. if (s._pluginsRegistered) {return false;}
  879. if (s.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin])) {return true;}
  880. return false;
  881. };
  882.  
  883. /**
  884. * Determines if Sound has been initialized, and a plugin has been activated.
  885. *
  886. * <h4>Example</h4>
  887. * This example sets up a Flash fallback, but only if there is no plugin specified yet.
  888. *
  889. * if (!createjs.Sound.isReady()) {
  890. * createjs.FlashAudioPlugin.swfPath = "../src/soundjs/flashaudio/";
  891. * createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.HTMLAudioPlugin, createjs.FlashAudioPlugin]);
  892. * }
  893. *
  894. * @method isReady
  895. * @return {Boolean} If Sound has initialized a plugin.
  896. * @static
  897. */
  898. s.isReady = function () {
  899. return (s.activePlugin != null);
  900. };
  901.  
  902. /**
  903. * Process manifest items from <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. This method is intended
  904. * for usage by a plugin, and not for direct interaction.
  905. * @method initLoad
  906. * @param {Object} src The object to load.
  907. * @return {Object|AbstractLoader} An instance of AbstractLoader.
  908. * @private
  909. * @static
  910. */
  911. s.initLoad = function (loadItem) {
  912. if (loadItem.type == "video") { return true; } // Don't handle video. PreloadJS's plugin model is really aggressive.
  913. return s._registerSound(loadItem);
  914. };
  915.  
  916. /**
  917. * Internal method for loading sounds. This should not be called directly.
  918. *
  919. * @method _registerSound
  920. * @param {Object} src The object to load, containing src property and optionally containing id and data.
  921. * @return {Object} An object with the modified values that were passed in, which defines the sound.
  922. * Returns false if the source cannot be parsed or no plugins can be initialized.
  923. * Returns true if the source is already loaded.
  924. * @static
  925. * @private
  926. * @since 0.6.0
  927. */
  928.  
  929. s._registerSound = function (loadItem) {
  930. if (!s.initializeDefaultPlugins()) {return false;}
  931.  
  932. var details;
  933. if (loadItem.src instanceof Object) {
  934. details = s._parseSrc(loadItem.src);
  935. details.src = loadItem.path + details.src;
  936. } else {
  937. details = s._parsePath(loadItem.src);
  938. }
  939. if (details == null) {return false;}
  940. loadItem.src = details.src;
  941. loadItem.type = "sound";
  942.  
  943. var data = loadItem.data;
  944. var numChannels = null;
  945. if (data != null) {
  946. if (!isNaN(data.channels)) {
  947. numChannels = parseInt(data.channels);
  948. } else if (!isNaN(data)) {
  949. numChannels = parseInt(data);
  950. }
  951.  
  952. if(data.audioSprite) {
  953. var sp;
  954. for(var i = data.audioSprite.length; i--; ) {
  955. sp = data.audioSprite[i];
  956. s._idHash[sp.id] = {src: loadItem.src, startTime: parseInt(sp.startTime), duration: parseInt(sp.duration)};
  957.  
  958. if (sp.defaultPlayProps) {
  959. s._defaultPlayPropsHash[sp.id] = createjs.PlayPropsConfig.create(sp.defaultPlayProps);
  960. }
  961. }
  962. }
  963. }
  964. if (loadItem.id != null) {s._idHash[loadItem.id] = {src: loadItem.src}};
  965. var loader = s.activePlugin.register(loadItem);
  966.  
  967. SoundChannel.create(loadItem.src, numChannels);
  968.  
  969. // return the number of instances to the user. This will also be returned in the load event.
  970. if (data == null || !isNaN(data)) {
  971. loadItem.data = numChannels || SoundChannel.maxPerChannel();
  972. } else {
  973. loadItem.data.channels = numChannels || SoundChannel.maxPerChannel();
  974. }
  975.  
  976. if (loader.type) {loadItem.type = loader.type;}
  977.  
  978. if (loadItem.defaultPlayProps) {
  979. s._defaultPlayPropsHash[loadItem.src] = createjs.PlayPropsConfig.create(loadItem.defaultPlayProps);
  980. }
  981. return loader;
  982. };
  983.  
  984. /**
  985. * Register an audio file for loading and future playback in Sound. This is automatically called when using
  986. * <a href="http://preloadjs.com" target="_blank">PreloadJS</a>. It is recommended to register all sounds that
  987. * need to be played back in order to properly prepare and preload them. Sound does internal preloading when required.
  988. *
  989. * <h4>Example</h4>
  990. *
  991. * createjs.Sound.alternateExtensions = ["mp3"];
  992. * createjs.Sound.on("fileload", handleLoad); // add an event listener for when load is completed
  993. * createjs.Sound.registerSound("myAudioPath/mySound.ogg", "myID", 3);
  994. * createjs.Sound.registerSound({ogg:"path1/mySound.ogg", mp3:"path2/mySoundNoExtension"}, "myID", 3);
  995. *
  996. *
  997. * @method registerSound
  998. * @param {String | Object} src The source or an Object with a "src" property or an Object with multiple extension labeled src properties.
  999. * @param {String} [id] An id specified by the user to play the sound later. Note id is required for when src is multiple extension labeled src properties.
  1000. * @param {Number | Object} [data] Data associated with the item. Sound uses the data parameter as the number of
  1001. * channels for an audio instance, however a "channels" property can be appended to the data object if it is used
  1002. * for other information. The audio channels will set a default based on plugin if no value is found.
  1003. * Sound also uses the data property to hold an {{#crossLink "AudioSprite"}}{{/crossLink}} array of objects in the following format {id, startTime, duration}.<br/>
  1004. * id used to play the sound later, in the same manner as a sound src with an id.<br/>
  1005. * startTime is the initial offset to start playback and loop from, in milliseconds.<br/>
  1006. * duration is the amount of time to play the clip for, in milliseconds.<br/>
  1007. * This allows Sound to support audio sprites that are played back by id.
  1008. * @param {string} basePath Set a path that will be prepended to src for loading.
  1009. * @param {Object | PlayPropsConfig} defaultPlayProps Optional Playback properties that will be set as the defaults on any new AbstractSoundInstance.
  1010. * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for options.
  1011. * @return {Object} An object with the modified values that were passed in, which defines the sound.
  1012. * Returns false if the source cannot be parsed or no plugins can be initialized.
  1013. * Returns true if the source is already loaded.
  1014. * @static
  1015. * @since 0.4.0
  1016. */
  1017. s.registerSound = function (src, id, data, basePath, defaultPlayProps) {
  1018. var loadItem = {src: src, id: id, data:data, defaultPlayProps:defaultPlayProps};
  1019. if (src instanceof Object && src.src) {
  1020. basePath = id;
  1021. loadItem = src;
  1022. }
  1023. loadItem = createjs.LoadItem.create(loadItem);
  1024. loadItem.path = basePath;
  1025.  
  1026. if (basePath != null && !(loadItem.src instanceof Object)) {loadItem.src = basePath + loadItem.src;}
  1027.  
  1028. var loader = s._registerSound(loadItem);
  1029. if(!loader) {return false;}
  1030.  
  1031. if (!s._preloadHash[loadItem.src]) { s._preloadHash[loadItem.src] = [];}
  1032. s._preloadHash[loadItem.src].push(loadItem);
  1033. if (s._preloadHash[loadItem.src].length == 1) {
  1034. // OJR note this will disallow reloading a sound if loading fails or the source changes
  1035. loader.on("complete", this._handleLoadComplete, this);
  1036. loader.on("error", this._handleLoadError, this);
  1037. s.activePlugin.preload(loader);
  1038. } else {
  1039. if (s._preloadHash[loadItem.src][0] == true) {return true;}
  1040. }
  1041.  
  1042. return loadItem;
  1043. };
  1044.  
  1045. /**
  1046. * Register an array of audio files for loading and future playback in Sound. It is recommended to register all
  1047. * sounds that need to be played back in order to properly prepare and preload them. Sound does internal preloading
  1048. * when required.
  1049. *
  1050. * <h4>Example</h4>
  1051. *
  1052. * var assetPath = "./myAudioPath/";
  1053. * var sounds = [
  1054. * {src:"asset0.ogg", id:"example"},
  1055. * {src:"asset1.ogg", id:"1", data:6},
  1056. * {src:"asset2.mp3", id:"works"}
  1057. * {src:{mp3:"path1/asset3.mp3", ogg:"path2/asset3NoExtension"}, id:"better"}
  1058. * ];
  1059. * createjs.Sound.alternateExtensions = ["mp3"]; // if the passed extension is not supported, try this extension
  1060. * createjs.Sound.on("fileload", handleLoad); // call handleLoad when each sound loads
  1061. * createjs.Sound.registerSounds(sounds, assetPath);
  1062. *
  1063. * @method registerSounds
  1064. * @param {Array} sounds An array of objects to load. Objects are expected to be in the format needed for
  1065. * {{#crossLink "Sound/registerSound"}}{{/crossLink}}: <code>{src:srcURI, id:ID, data:Data}</code>
  1066. * with "id" and "data" being optional.
  1067. * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to load.
  1068. * Note id is required if src is an object with extension labeled src properties.
  1069. * @param {string} basePath Set a path that will be prepended to each src when loading. When creating, playing, or removing
  1070. * audio that was loaded with a basePath by src, the basePath must be included.
  1071. * @return {Object} An array of objects with the modified values that were passed in, which defines each sound.
  1072. * Like registerSound, it will return false for any values when the source cannot be parsed or if no plugins can be initialized.
  1073. * Also, it will return true for any values when the source is already loaded.
  1074. * @static
  1075. * @since 0.6.0
  1076. */
  1077. s.registerSounds = function (sounds, basePath) {
  1078. var returnValues = [];
  1079. if (sounds.path) {
  1080. if (!basePath) {
  1081. basePath = sounds.path;
  1082. } else {
  1083. basePath = basePath + sounds.path;
  1084. }
  1085. sounds = sounds.manifest;
  1086. // TODO document this feature
  1087. }
  1088. for (var i = 0, l = sounds.length; i < l; i++) {
  1089. returnValues[i] = createjs.Sound.registerSound(sounds[i].src, sounds[i].id, sounds[i].data, basePath, sounds[i].defaultPlayProps);
  1090. }
  1091. return returnValues;
  1092. };
  1093.  
  1094. /**
  1095. * Remove a sound that has been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
  1096. * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
  1097. * <br />Note this will stop playback on active instances playing this sound before deleting them.
  1098. * <br />Note if you passed in a basePath, you need to pass it or prepend it to the src here.
  1099. *
  1100. * <h4>Example</h4>
  1101. *
  1102. * createjs.Sound.removeSound("myID");
  1103. * createjs.Sound.removeSound("myAudioBasePath/mySound.ogg");
  1104. * createjs.Sound.removeSound("myPath/myOtherSound.mp3", "myBasePath/");
  1105. * createjs.Sound.removeSound({mp3:"musicNoExtension", ogg:"music.ogg"}, "myBasePath/");
  1106. *
  1107. * @method removeSound
  1108. * @param {String | Object} src The src or ID of the audio, or an Object with a "src" property, or an Object with multiple extension labeled src properties.
  1109. * @param {string} basePath Set a path that will be prepended to each src when removing.
  1110. * @return {Boolean} True if sound is successfully removed.
  1111. * @static
  1112. * @since 0.4.1
  1113. */
  1114. s.removeSound = function(src, basePath) {
  1115. if (s.activePlugin == null) {return false;}
  1116.  
  1117. if (src instanceof Object && src.src) {src = src.src;}
  1118.  
  1119. var details;
  1120. if (src instanceof Object) {
  1121. details = s._parseSrc(src);
  1122. } else {
  1123. src = s._getSrcById(src).src;
  1124. details = s._parsePath(src);
  1125. }
  1126. if (details == null) {return false;}
  1127. src = details.src;
  1128. if (basePath != null) {src = basePath + src;}
  1129.  
  1130. for(var prop in s._idHash){
  1131. if(s._idHash[prop].src == src) {
  1132. delete(s._idHash[prop]);
  1133. }
  1134. }
  1135.  
  1136. // clear from SoundChannel, which also stops and deletes all instances
  1137. SoundChannel.removeSrc(src);
  1138.  
  1139. delete(s._preloadHash[src]);
  1140.  
  1141. s.activePlugin.removeSound(src);
  1142.  
  1143. return true;
  1144. };
  1145.  
  1146. /**
  1147. * Remove an array of audio files that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
  1148. * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
  1149. * <br />Note this will stop playback on active instances playing this audio before deleting them.
  1150. * <br />Note if you passed in a basePath, you need to pass it or prepend it to the src here.
  1151. *
  1152. * <h4>Example</h4>
  1153. *
  1154. * assetPath = "./myPath/";
  1155. * var sounds = [
  1156. * {src:"asset0.ogg", id:"example"},
  1157. * {src:"asset1.ogg", id:"1", data:6},
  1158. * {src:"asset2.mp3", id:"works"}
  1159. * ];
  1160. * createjs.Sound.removeSounds(sounds, assetPath);
  1161. *
  1162. * @method removeSounds
  1163. * @param {Array} sounds An array of objects to remove. Objects are expected to be in the format needed for
  1164. * {{#crossLink "Sound/removeSound"}}{{/crossLink}}: <code>{srcOrID:srcURIorID}</code>.
  1165. * You can also pass an object with path and manifest properties, where path is a basePath and manifest is an array of objects to remove.
  1166. * @param {string} basePath Set a path that will be prepended to each src when removing.
  1167. * @return {Object} An array of Boolean values representing if the sounds with the same array index were
  1168. * successfully removed.
  1169. * @static
  1170. * @since 0.4.1
  1171. */
  1172. s.removeSounds = function (sounds, basePath) {
  1173. var returnValues = [];
  1174. if (sounds.path) {
  1175. if (!basePath) {
  1176. basePath = sounds.path;
  1177. } else {
  1178. basePath = basePath + sounds.path;
  1179. }
  1180. sounds = sounds.manifest;
  1181. }
  1182. for (var i = 0, l = sounds.length; i < l; i++) {
  1183. returnValues[i] = createjs.Sound.removeSound(sounds[i].src, basePath);
  1184. }
  1185. return returnValues;
  1186. };
  1187.  
  1188. /**
  1189. * Remove all sounds that have been registered with {{#crossLink "Sound/registerSound"}}{{/crossLink}} or
  1190. * {{#crossLink "Sound/registerSounds"}}{{/crossLink}}.
  1191. * <br />Note this will stop playback on all active sound instances before deleting them.
  1192. *
  1193. * <h4>Example</h4>
  1194. *
  1195. * createjs.Sound.removeAllSounds();
  1196. *
  1197. * @method removeAllSounds
  1198. * @static
  1199. * @since 0.4.1
  1200. */
  1201. s.removeAllSounds = function() {
  1202. s._idHash = {};
  1203. s._preloadHash = {};
  1204. SoundChannel.removeAll();
  1205. if (s.activePlugin) {s.activePlugin.removeAllSounds();}
  1206. };
  1207.  
  1208. /**
  1209. * Check if a source has been loaded by internal preloaders. This is necessary to ensure that sounds that are
  1210. * not completed preloading will not kick off a new internal preload if they are played.
  1211. *
  1212. * <h4>Example</h4>
  1213. *
  1214. * var mySound = "assetPath/asset0.ogg";
  1215. * if(createjs.Sound.loadComplete(mySound) {
  1216. * createjs.Sound.play(mySound);
  1217. * }
  1218. *
  1219. * @method loadComplete
  1220. * @param {String} src The src or id that is being loaded.
  1221. * @return {Boolean} If the src is already loaded.
  1222. * @since 0.4.0
  1223. * @static
  1224. */
  1225. s.loadComplete = function (src) {
  1226. if (!s.isReady()) { return false; }
  1227. var details = s._parsePath(src);
  1228. if (details) {
  1229. src = s._getSrcById(details.src).src;
  1230. } else {
  1231. src = s._getSrcById(src).src;
  1232. }
  1233. if(s._preloadHash[src] == undefined) {return false;}
  1234. return (s._preloadHash[src][0] == true); // src only loads once, so if it's true for the first it's true for all
  1235. };
  1236.  
  1237. /**
  1238. * Parse the path of a sound. Alternate extensions will be attempted in order if the
  1239. * current extension is not supported
  1240. * @method _parsePath
  1241. * @param {String} value The path to an audio source.
  1242. * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}}
  1243. * and returned to a preloader like <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
  1244. * @private
  1245. * @static
  1246. */
  1247. s._parsePath = function (value) {
  1248. if (typeof(value) != "string") {value = value.toString();}
  1249.  
  1250. var match = value.match(s.FILE_PATTERN);
  1251. if (match == null) {return false;}
  1252.  
  1253. var name = match[4];
  1254. var ext = match[5];
  1255. var c = s.capabilities;
  1256. var i = 0;
  1257. while (!c[ext]) {
  1258. ext = s.alternateExtensions[i++];
  1259. if (i > s.alternateExtensions.length) { return null;} // no extensions are supported
  1260. }
  1261. value = value.replace("."+match[5], "."+ext);
  1262.  
  1263. var ret = {name:name, src:value, extension:ext};
  1264. return ret;
  1265. };
  1266.  
  1267. /**
  1268. * Parse the path of a sound based on properties of src matching with supported extensions.
  1269. * Returns false if none of the properties are supported
  1270. * @method _parseSrc
  1271. * @param {Object} value The paths to an audio source, indexed by extension type.
  1272. * @return {Object} A formatted object that can be registered with the {{#crossLink "Sound/activePlugin:property"}}{{/crossLink}}
  1273. * and returned to a preloader like <a href="http://preloadjs.com" target="_blank">PreloadJS</a>.
  1274. * @private
  1275. * @static
  1276. */
  1277. s._parseSrc = function (value) {
  1278. var ret = {name:undefined, src:undefined, extension:undefined};
  1279. var c = s.capabilities;
  1280.  
  1281. for (var prop in value) {
  1282. if(value.hasOwnProperty(prop) && c[prop]) {
  1283. ret.src = value[prop];
  1284. ret.extension = prop;
  1285. break;
  1286. }
  1287. }
  1288. if (!ret.src) {return false;} // no matches
  1289.  
  1290. var i = ret.src.lastIndexOf("/");
  1291. if (i != -1) {
  1292. ret.name = ret.src.slice(i+1);
  1293. } else {
  1294. ret.name = ret.src;
  1295. }
  1296.  
  1297. return ret;
  1298. };
  1299.  
  1300. /* ---------------
  1301. Static API.
  1302. --------------- */
  1303. /**
  1304. * Play a sound and get a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to control. If the sound fails to
  1305. * play, an AbstractSoundInstance will still be returned, and have a playState of {{#crossLink "Sound/PLAY_FAILED:property"}}{{/crossLink}}.
  1306. * Note that even on sounds with failed playback, you may still be able to call the {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}},
  1307. * method, since the failure could be due to lack of available channels. If the src does not have a supported
  1308. * extension or if there is no available plugin, a default AbstractSoundInstance will still be returned, which will
  1309. * not play any audio, but will not generate errors.
  1310. *
  1311. * <h4>Example</h4>
  1312. *
  1313. * createjs.Sound.on("fileload", handleLoad);
  1314. * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3);
  1315. * function handleLoad(event) {
  1316. * createjs.Sound.play("myID");
  1317. * // store off AbstractSoundInstance for controlling
  1318. * var myInstance = createjs.Sound.play("myID", {interrupt: createjs.Sound.INTERRUPT_ANY, loop:-1});
  1319. * }
  1320. *
  1321. * NOTE: To create an audio sprite that has not already been registered, both startTime and duration need to be set.
  1322. * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite.
  1323. *
  1324. * @method play
  1325. * @param {String} src The src or ID of the audio.
  1326. * @param {Object | PlayPropsConfig} props A PlayPropsConfig instance, or an object that contains the parameters to
  1327. * play a sound. See the {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for more info.
  1328. * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled
  1329. * after it is created.
  1330. * @static
  1331. */
  1332. s.play = function (src, props) {
  1333. var playProps = createjs.PlayPropsConfig.create(props);
  1334. var instance = s.createInstance(src, playProps.startTime, playProps.duration);
  1335. var ok = s._playInstance(instance, playProps);
  1336. if (!ok) {instance._playFailed();}
  1337. return instance;
  1338. };
  1339.  
  1340. /**
  1341. * Creates a {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} using the passed in src. If the src does not have a
  1342. * supported extension or if there is no available plugin, a default AbstractSoundInstance will be returned that can be
  1343. * called safely but does nothing.
  1344. *
  1345. * <h4>Example</h4>
  1346. *
  1347. * var myInstance = null;
  1348. * createjs.Sound.on("fileload", handleLoad);
  1349. * createjs.Sound.registerSound("myAudioPath/mySound.mp3", "myID", 3);
  1350. * function handleLoad(event) {
  1351. * myInstance = createjs.Sound.createInstance("myID");
  1352. * // alternately we could call the following
  1353. * myInstance = createjs.Sound.createInstance("myAudioPath/mySound.mp3");
  1354. * }
  1355. *
  1356. * NOTE to create an audio sprite that has not already been registered, both startTime and duration need to be set.
  1357. * This is only when creating a new audio sprite, not when playing using the id of an already registered audio sprite.
  1358. *
  1359. * @method createInstance
  1360. * @param {String} src The src or ID of the audio.
  1361. * @param {Number} [startTime=null] To create an audio sprite (with duration), the initial offset to start playback and loop from, in milliseconds.
  1362. * @param {Number} [duration=null] To create an audio sprite (with startTime), the amount of time to play the clip for, in milliseconds.
  1363. * @return {AbstractSoundInstance} A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} that can be controlled after it is created.
  1364. * Unsupported extensions will return the default AbstractSoundInstance.
  1365. * @since 0.4.0
  1366. * @static
  1367. */
  1368. s.createInstance = function (src, startTime, duration) {
  1369. if (!s.initializeDefaultPlugins()) { return new createjs.DefaultSoundInstance(src, startTime, duration); }
  1370.  
  1371. var defaultPlayProps = s._defaultPlayPropsHash[src]; // for audio sprites, which create and store defaults by id
  1372. src = s._getSrcById(src);
  1373.  
  1374. var details = s._parsePath(src.src);
  1375.  
  1376. var instance = null;
  1377. if (details != null && details.src != null) {
  1378. SoundChannel.create(details.src);
  1379. if (startTime == null) { startTime = src.startTime; }
  1380. instance = s.activePlugin.create(details.src, startTime, duration || src.duration);
  1381.  
  1382. defaultPlayProps = defaultPlayProps || s._defaultPlayPropsHash[details.src];
  1383. if (defaultPlayProps) {
  1384. instance.applyPlayProps(defaultPlayProps);
  1385. }
  1386. } else {
  1387. instance = new createjs.DefaultSoundInstance(src, startTime, duration);
  1388. }
  1389.  
  1390. instance.uniqueId = s._lastID++;
  1391.  
  1392. return instance;
  1393. };
  1394.  
  1395. /**
  1396. * Stop all audio (global stop). Stopped audio is reset, and not paused. To play audio that has been stopped,
  1397. * call AbstractSoundInstance {{#crossLink "AbstractSoundInstance/play"}}{{/crossLink}}.
  1398. *
  1399. * <h4>Example</h4>
  1400. *
  1401. * createjs.Sound.stop();
  1402. *
  1403. * @method stop
  1404. * @static
  1405. */
  1406. s.stop = function () {
  1407. var instances = this._instances;
  1408. for (var i = instances.length; i--; ) {
  1409. instances[i].stop(); // NOTE stop removes instance from this._instances
  1410. }
  1411. };
  1412.  
  1413. /**
  1414. * Set the default playback properties for all new SoundInstances of the passed in src or ID.
  1415. * See {{#crossLink "PlayPropsConfig"}}{{/crossLink}} for available properties.
  1416. *
  1417. * @method setDefaultPlayProps
  1418. * @param {String} src The src or ID used to register the audio.
  1419. * @param {Object | PlayPropsConfig} playProps The playback properties you would like to set.
  1420. * @since 0.6.1
  1421. */
  1422. s.setDefaultPlayProps = function(src, playProps) {
  1423. src = s._getSrcById(src);
  1424. s._defaultPlayPropsHash[s._parsePath(src.src).src] = createjs.PlayPropsConfig.create(playProps);
  1425. };
  1426.  
  1427. /**
  1428. * Get the default playback properties for the passed in src or ID. These properties are applied to all
  1429. * new SoundInstances. Returns null if default does not exist.
  1430. *
  1431. * @method getDefaultPlayProps
  1432. * @param {String} src The src or ID used to register the audio.
  1433. * @returns {PlayPropsConfig} returns an existing PlayPropsConfig or null if one does not exist
  1434. * @since 0.6.1
  1435. */
  1436. s.getDefaultPlayProps = function(src) {
  1437. src = s._getSrcById(src);
  1438. return s._defaultPlayPropsHash[s._parsePath(src.src).src];
  1439. };
  1440.  
  1441.  
  1442. /* ---------------
  1443. Internal methods
  1444. --------------- */
  1445. /**
  1446. * Play an instance. This is called by the static API, as well as from plugins. This allows the core class to
  1447. * control delays.
  1448. * @method _playInstance
  1449. * @param {AbstractSoundInstance} instance The {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to start playing.
  1450. * @param {PlayPropsConfig} playProps A PlayPropsConfig object.
  1451. * @return {Boolean} If the sound can start playing. Sounds that fail immediately will return false. Sounds that
  1452. * have a delay will return true, but may still fail to play.
  1453. * @private
  1454. * @static
  1455. */
  1456. s._playInstance = function (instance, playProps) {
  1457. var defaultPlayProps = s._defaultPlayPropsHash[instance.src] || {};
  1458. if (playProps.interrupt == null) {playProps.interrupt = defaultPlayProps.interrupt || s.defaultInterruptBehavior};
  1459. if (playProps.delay == null) {playProps.delay = defaultPlayProps.delay || 0;}
  1460. if (playProps.offset == null) {playProps.offset = instance.position;}
  1461. if (playProps.loop == null) {playProps.loop = instance.loop;}
  1462. if (playProps.volume == null) {playProps.volume = instance.volume;}
  1463. if (playProps.pan == null) {playProps.pan = instance.pan;}
  1464.  
  1465. if (playProps.delay == 0) {
  1466. var ok = s._beginPlaying(instance, playProps);
  1467. if (!ok) {return false;}
  1468. } else {
  1469. //Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call.
  1470. // OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future
  1471. var delayTimeoutId = setTimeout(function () {
  1472. s._beginPlaying(instance, playProps);
  1473. }, playProps.delay);
  1474. instance.delayTimeoutId = delayTimeoutId;
  1475. }
  1476.  
  1477. this._instances.push(instance);
  1478.  
  1479. return true;
  1480. };
  1481.  
  1482. /**
  1483. * Begin playback. This is called immediately or after delay by {{#crossLink "Sound/playInstance"}}{{/crossLink}}.
  1484. * @method _beginPlaying
  1485. * @param {AbstractSoundInstance} instance A {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} to begin playback.
  1486. * @param {PlayPropsConfig} playProps A PlayPropsConfig object.
  1487. * @return {Boolean} If the sound can start playing. If there are no available channels, or the instance fails to
  1488. * start, this will return false.
  1489. * @private
  1490. * @static
  1491. */
  1492. s._beginPlaying = function (instance, playProps) {
  1493. if (!SoundChannel.add(instance, playProps.interrupt)) {
  1494. return false;
  1495. }
  1496. var result = instance._beginPlaying(playProps);
  1497. if (!result) {
  1498. var index = createjs.indexOf(this._instances, instance);
  1499. if (index > -1) {this._instances.splice(index, 1);}
  1500. return false;
  1501. }
  1502. return true;
  1503. };
  1504.  
  1505. /**
  1506. * Get the source of a sound via the ID passed in with a register call. If no ID is found the value is returned
  1507. * instead.
  1508. * @method _getSrcById
  1509. * @param {String} value The ID the sound was registered with.
  1510. * @return {String} The source of the sound if it has been registered with this ID or the value that was passed in.
  1511. * @private
  1512. * @static
  1513. */
  1514. s._getSrcById = function (value) {
  1515. return s._idHash[value] || {src: value};
  1516. };
  1517.  
  1518. /**
  1519. * A sound has completed playback, been interrupted, failed, or been stopped. This method removes the instance from
  1520. * Sound management. It will be added again, if the sound re-plays. Note that this method is called from the
  1521. * instances themselves.
  1522. * @method _playFinished
  1523. * @param {AbstractSoundInstance} instance The instance that finished playback.
  1524. * @private
  1525. * @static
  1526. */
  1527. s._playFinished = function (instance) {
  1528. SoundChannel.remove(instance);
  1529. var index = createjs.indexOf(this._instances, instance);
  1530. if (index > -1) {this._instances.splice(index, 1);} // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances
  1531. };
  1532.  
  1533. createjs.Sound = Sound;
  1534.  
  1535. /**
  1536. * An internal class that manages the number of active {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} instances for
  1537. * each sound type. This method is only used internally by the {{#crossLink "Sound"}}{{/crossLink}} class.
  1538. *
  1539. * The number of sounds is artificially limited by Sound in order to prevent over-saturation of a
  1540. * single sound, as well as to stay within hardware limitations, although the latter may disappear with better
  1541. * browser support.
  1542. *
  1543. * When a sound is played, this class ensures that there is an available instance, or interrupts an appropriate
  1544. * sound that is already playing.
  1545. * #class SoundChannel
  1546. * @param {String} src The source of the instances
  1547. * @param {Number} [max=1] The number of instances allowed
  1548. * @constructor
  1549. * @protected
  1550. */
  1551. function SoundChannel(src, max) {
  1552. this.init(src, max);
  1553. }
  1554.  
  1555. /* ------------
  1556. Static API
  1557. ------------ */
  1558. /**
  1559. * A hash of channel instances indexed by source.
  1560. * #property channels
  1561. * @type {Object}
  1562. * @static
  1563. */
  1564. SoundChannel.channels = {};
  1565.  
  1566. /**
  1567. * Create a sound channel. Note that if the sound channel already exists, this will fail.
  1568. * #method create
  1569. * @param {String} src The source for the channel
  1570. * @param {Number} max The maximum amount this channel holds. The default is {{#crossLink "SoundChannel.maxDefault"}}{{/crossLink}}.
  1571. * @return {Boolean} If the channels were created.
  1572. * @static
  1573. */
  1574. SoundChannel.create = function (src, max) {
  1575. var channel = SoundChannel.get(src);
  1576. if (channel == null) {
  1577. SoundChannel.channels[src] = new SoundChannel(src, max);
  1578. return true;
  1579. }
  1580. return false;
  1581. };
  1582. /**
  1583. * Delete a sound channel, stop and delete all related instances. Note that if the sound channel does not exist, this will fail.
  1584. * #method remove
  1585. * @param {String} src The source for the channel
  1586. * @return {Boolean} If the channels were deleted.
  1587. * @static
  1588. */
  1589. SoundChannel.removeSrc = function (src) {
  1590. var channel = SoundChannel.get(src);
  1591. if (channel == null) {return false;}
  1592. channel._removeAll(); // this stops and removes all active instances
  1593. delete(SoundChannel.channels[src]);
  1594. return true;
  1595. };
  1596. /**
  1597. * Delete all sound channels, stop and delete all related instances.
  1598. * #method removeAll
  1599. * @static
  1600. */
  1601. SoundChannel.removeAll = function () {
  1602. for(var channel in SoundChannel.channels) {
  1603. SoundChannel.channels[channel]._removeAll(); // this stops and removes all active instances
  1604. }
  1605. SoundChannel.channels = {};
  1606. };
  1607. /**
  1608. * Add an instance to a sound channel.
  1609. * #method add
  1610. * @param {AbstractSoundInstance} instance The instance to add to the channel
  1611. * @param {String} interrupt The interrupt value to use. Please see the {{#crossLink "Sound/play"}}{{/crossLink}}
  1612. * for details on interrupt modes.
  1613. * @return {Boolean} The success of the method call. If the channel is full, it will return false.
  1614. * @static
  1615. */
  1616. SoundChannel.add = function (instance, interrupt) {
  1617. var channel = SoundChannel.get(instance.src);
  1618. if (channel == null) {return false;}
  1619. return channel._add(instance, interrupt);
  1620. };
  1621. /**
  1622. * Remove an instance from the channel.
  1623. * #method remove
  1624. * @param {AbstractSoundInstance} instance The instance to remove from the channel
  1625. * @return The success of the method call. If there is no channel, it will return false.
  1626. * @static
  1627. */
  1628. SoundChannel.remove = function (instance) {
  1629. var channel = SoundChannel.get(instance.src);
  1630. if (channel == null) {return false;}
  1631. channel._remove(instance);
  1632. return true;
  1633. };
  1634. /**
  1635. * Get the maximum number of sounds you can have in a channel.
  1636. * #method maxPerChannel
  1637. * @return {Number} The maximum number of sounds you can have in a channel.
  1638. */
  1639. SoundChannel.maxPerChannel = function () {
  1640. return p.maxDefault;
  1641. };
  1642. /**
  1643. * Get a channel instance by its src.
  1644. * #method get
  1645. * @param {String} src The src to use to look up the channel
  1646. * @static
  1647. */
  1648. SoundChannel.get = function (src) {
  1649. return SoundChannel.channels[src];
  1650. };
  1651.  
  1652. var p = SoundChannel.prototype;
  1653. p.constructor = SoundChannel;
  1654.  
  1655. /**
  1656. * The source of the channel.
  1657. * #property src
  1658. * @type {String}
  1659. */
  1660. p.src = null;
  1661.  
  1662. /**
  1663. * The maximum number of instances in this channel. -1 indicates no limit
  1664. * #property max
  1665. * @type {Number}
  1666. */
  1667. p.max = null;
  1668.  
  1669. /**
  1670. * The default value to set for max, if it isn't passed in. Also used if -1 is passed.
  1671. * #property maxDefault
  1672. * @type {Number}
  1673. * @default 100
  1674. * @since 0.4.0
  1675. */
  1676. p.maxDefault = 100;
  1677.  
  1678. /**
  1679. * The current number of active instances.
  1680. * #property length
  1681. * @type {Number}
  1682. */
  1683. p.length = 0;
  1684.  
  1685. /**
  1686. * Initialize the channel.
  1687. * #method init
  1688. * @param {String} src The source of the channel
  1689. * @param {Number} max The maximum number of instances in the channel
  1690. * @protected
  1691. */
  1692. p.init = function (src, max) {
  1693. this.src = src;
  1694. this.max = max || this.maxDefault;
  1695. if (this.max == -1) {this.max = this.maxDefault;}
  1696. this._instances = [];
  1697. };
  1698.  
  1699. /**
  1700. * Get an instance by index.
  1701. * #method get
  1702. * @param {Number} index The index to return.
  1703. * @return {AbstractSoundInstance} The AbstractSoundInstance at a specific instance.
  1704. */
  1705. p._get = function (index) {
  1706. return this._instances[index];
  1707. };
  1708.  
  1709. /**
  1710. * Add a new instance to the channel.
  1711. * #method add
  1712. * @param {AbstractSoundInstance} instance The instance to add.
  1713. * @return {Boolean} The success of the method call. If the channel is full, it will return false.
  1714. */
  1715. p._add = function (instance, interrupt) {
  1716. if (!this._getSlot(interrupt, instance)) {return false;}
  1717. this._instances.push(instance);
  1718. this.length++;
  1719. return true;
  1720. };
  1721.  
  1722. /**
  1723. * Remove an instance from the channel, either when it has finished playing, or it has been interrupted.
  1724. * #method remove
  1725. * @param {AbstractSoundInstance} instance The instance to remove
  1726. * @return {Boolean} The success of the remove call. If the instance is not found in this channel, it will
  1727. * return false.
  1728. */
  1729. p._remove = function (instance) {
  1730. var index = createjs.indexOf(this._instances, instance);
  1731. if (index == -1) {return false;}
  1732. this._instances.splice(index, 1);
  1733. this.length--;
  1734. return true;
  1735. };
  1736.  
  1737. /**
  1738. * Stop playback and remove all instances from the channel. Usually in response to a delete call.
  1739. * #method removeAll
  1740. */
  1741. p._removeAll = function () {
  1742. // Note that stop() removes the item from the list
  1743. for (var i=this.length-1; i>=0; i--) {
  1744. this._instances[i].stop();
  1745. }
  1746. };
  1747.  
  1748. /**
  1749. * Get an available slot depending on interrupt value and if slots are available.
  1750. * #method getSlot
  1751. * @param {String} interrupt The interrupt value to use.
  1752. * @param {AbstractSoundInstance} instance The sound instance that will go in the channel if successful.
  1753. * @return {Boolean} Determines if there is an available slot. Depending on the interrupt mode, if there are no slots,
  1754. * an existing AbstractSoundInstance may be interrupted. If there are no slots, this method returns false.
  1755. */
  1756. p._getSlot = function (interrupt, instance) {
  1757. var target, replacement;
  1758.  
  1759. if (interrupt != Sound.INTERRUPT_NONE) {
  1760. // First replacement candidate
  1761. replacement = this._get(0);
  1762. if (replacement == null) {
  1763. return true;
  1764. }
  1765. }
  1766.  
  1767. for (var i = 0, l = this.max; i < l; i++) {
  1768. target = this._get(i);
  1769.  
  1770. // Available Space
  1771. if (target == null) {
  1772. return true;
  1773. }
  1774.  
  1775. // Audio is complete or not playing
  1776. if (target.playState == Sound.PLAY_FINISHED ||
  1777. target.playState == Sound.PLAY_INTERRUPTED ||
  1778. target.playState == Sound.PLAY_FAILED) {
  1779. replacement = target;
  1780. break;
  1781. }
  1782.  
  1783. if (interrupt == Sound.INTERRUPT_NONE) {
  1784. continue;
  1785. }
  1786.  
  1787. // Audio is a better candidate than the current target, according to playhead
  1788. if ((interrupt == Sound.INTERRUPT_EARLY && target.position < replacement.position) ||
  1789. (interrupt == Sound.INTERRUPT_LATE && target.position > replacement.position)) {
  1790. replacement = target;
  1791. }
  1792. }
  1793.  
  1794. if (replacement != null) {
  1795. replacement._interrupt();
  1796. this._remove(replacement);
  1797. return true;
  1798. }
  1799. return false;
  1800. };
  1801.  
  1802. p.toString = function () {
  1803. return "[Sound SoundChannel]";
  1804. };
  1805. // do not add SoundChannel to namespace
  1806.  
  1807. }());
  1808.