UNPKG

93.6 kBHTMLView Raw
1<!DOCTYPE html>
2
3<html lang="en">
4<head>
5 <meta charset="utf-8">
6 <meta name="viewport" content="width=device-width">
7 <title>CrossBrowdy API documentation Source: CrossBase/audiovisual/audio/CB_AudioFile_API_WAAPI.js</title>
8
9 <!--[if lt IE 9]>
10 <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
11 <![endif]-->
12 <link type="text/css" rel="stylesheet" href="styles/sunlight.default.css">
13
14 <link type="text/css" rel="stylesheet" href="styles/site.cosmo.css">
15
16</head>
17
18<body style="min-width:800px; overflow-wrap:break-word; word-wrap:break-word; word-break:break-word; line-break:strict; hyphens:none; -webkit-hyphens:none; -moz-hyphens:none;">
19
20<div class="navbar navbar-default navbar-fixed-top ">
21<div class="container">
22 <div class="navbar-header">
23 <a class="navbar-brand" href="index.html">CrossBrowdy API documentation</a>
24 <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#topNavigation">
25 <span class="icon-bar"></span>
26 <span class="icon-bar"></span>
27 <span class="icon-bar"></span>
28 </button>
29 </div>
30 <div class="navbar-collapse collapse" id="topNavigation">
31 <ul class="nav navbar-nav">
32
33 <li class="dropdown">
34 <a href="namespaces.list.html" class="dropdown-toggle" data-toggle="dropdown">Namespaces<b class="caret"></b></a>
35 <ul class="dropdown-menu inline">
36 <li><a href="CB_Arrays.html">CB_Arrays</a></li><li><a href="CB_AudioDetector.html">CB_AudioDetector</a></li><li><a href="CB_Client.html">CB_Client</a></li><li><a href="CB_Collisions.html">CB_Collisions</a></li><li><a href="CB_Configuration.html">CB_Configuration</a></li><li><a href="CB_Configuration.CrossBase.html">CB_Configuration.CrossBase</a></li><li><a href="CB_Configuration.CrossBrowdy.html">CB_Configuration.CrossBrowdy</a></li><li><a href="CB_Controllers.html">CB_Controllers</a></li><li><a href="CB_Controllers_Proprietary.html">CB_Controllers_Proprietary</a></li><li><a href="CB_Controllers_Proprietary.WII.html">CB_Controllers_Proprietary.WII</a></li><li><a href="CB_Controllers_Proprietary.WII_U.html">CB_Controllers_Proprietary.WII_U</a></li><li><a href="CB_Device.html">CB_Device</a></li><li><a href="CB_Device.AmbientLight.html">CB_Device.AmbientLight</a></li><li><a href="CB_Device.Battery.html">CB_Device.Battery</a></li><li><a href="CB_Device.Location.html">CB_Device.Location</a></li><li><a href="CB_Device.Motion.html">CB_Device.Motion</a></li><li><a href="CB_Device.Orientation.html">CB_Device.Orientation</a></li><li><a href="CB_Device.Proximity.html">CB_Device.Proximity</a></li><li><a href="CB_Device.Vibration.html">CB_Device.Vibration</a></li><li><a href="CB_Elements.html">CB_Elements</a></li><li><a href="CB_Events.html">CB_Events</a></li><li><a href="CB_Keyboard.html">CB_Keyboard</a></li><li><a href="CB_Keyboard.chars.html">CB_Keyboard.chars</a></li><li><a href="CB_Keyboard.extended.html">CB_Keyboard.extended</a></li><li><a href="CB_Keyboard.keys.html">CB_Keyboard.keys</a></li><li><a href="CB_Modules.html">CB_Modules</a></li><li><a href="CB_Mouse.html">CB_Mouse</a></li><li><a href="CB_Mouse.CursorImage.html">CB_Mouse.CursorImage</a></li><li><a href="CB_Net.html">CB_Net</a></li><li><a href="CB_Net.Fetch.html">CB_Net.Fetch</a></li><li><a href="CB_Net.REST.html">CB_Net.REST</a></li><li><a href="CB_Net.Sockets.html">CB_Net.Sockets</a></li><li><a href="CB_Net.Sockets.SockJS.html">CB_Net.Sockets.SockJS</a></li><li><a href="CB_Net.XHR.html">CB_Net.XHR</a></li><li><a href="CB_Pointer.html">CB_Pointer</a></li><li><a href="CB_Screen.html">CB_Screen</a></li><li><a href="CB_Speaker.html">CB_Speaker</a></li><li><a href="CB_Touch.html">CB_Touch</a></li><li><a href="CB_baseSymbols.html">CB_baseSymbols</a></li>
37 </ul>
38 </li>
39
40 <li class="dropdown">
41 <a href="classes.list.html" class="dropdown-toggle" data-toggle="dropdown">Classes<b class="caret"></b></a>
42 <ul class="dropdown-menu inline">
43 <li><a href="CB_AudioFile.html">CB_AudioFile</a></li><li><a href="CB_AudioFileCache.html">CB_AudioFileCache</a></li><li><a href="CB_AudioFileSprites.html">CB_AudioFileSprites</a></li><li><a href="CB_AudioFileSpritesPool.html">CB_AudioFileSpritesPool</a></li><li><a href="CB_AudioFile_API.AAPI.html">CB_AudioFile_API.AAPI</a></li><li><a href="CB_AudioFile_API.ACMP.html">CB_AudioFile_API.ACMP</a></li><li><a href="CB_AudioFile_API.SM2.html">CB_AudioFile_API.SM2</a></li><li><a href="CB_AudioFile_API.WAAPI.html">CB_AudioFile_API.WAAPI</a></li><li><a href="CB_Canvas.html">CB_Canvas</a></li><li><a href="CB_GraphicSprites.html">CB_GraphicSprites</a></li><li><a href="CB_GraphicSpritesScene.html">CB_GraphicSpritesScene</a></li>
44 </ul>
45 </li>
46
47 <li class="dropdown">
48 <a href="global.html" class="dropdown-toggle" data-toggle="dropdown">Global<b class="caret"></b></a>
49 <ul class="dropdown-menu inline">
50 <li><a href="global.html#CB_BASE_NAME">CB_BASE_NAME</a></li><li><a href="global.html#CB_CREDITS_DEFAULT">CB_CREDITS_DEFAULT</a></li><li><a href="global.html#CB_NAME">CB_NAME</a></li><li><a href="global.html#CB_OPTIONS">CB_OPTIONS</a></li><li><a href="global.html#CB_VERSION">CB_VERSION</a></li><li><a href="global.html#CB_addCredits">CB_addCredits</a></li><li><a href="global.html#CB_baseToBase">CB_baseToBase</a></li><li><a href="global.html#CB_baseToInt">CB_baseToInt</a></li><li><a href="global.html#CB_br2nl">CB_br2nl</a></li><li><a href="global.html#CB_brToNl">CB_brToNl</a></li><li><a href="global.html#CB_combineArraysOrObjects">CB_combineArraysOrObjects</a></li><li><a href="global.html#CB_combineAutomatically">CB_combineAutomatically</a></li><li><a href="global.html#CB_combineJSON">CB_combineJSON</a></li><li><a href="global.html#CB_combineURIParameters">CB_combineURIParameters</a></li><li><a href="global.html#CB_combineURLParameters">CB_combineURLParameters</a></li><li><a href="global.html#CB_console">CB_console</a></li><li><a href="global.html#CB_copyObject">CB_copyObject</a></li><li><a href="global.html#CB_countDecimalDigits">CB_countDecimalDigits</a></li><li><a href="global.html#CB_countDecimalPart">CB_countDecimalPart</a></li><li><a href="global.html#CB_countDecimals">CB_countDecimals</a></li><li><a href="global.html#CB_countIntegerDigits">CB_countIntegerDigits</a></li><li><a href="global.html#CB_countIntegerPart">CB_countIntegerPart</a></li><li><a href="global.html#CB_credits">CB_credits</a></li><li><a href="global.html#CB_forEach">CB_forEach</a></li><li><a href="global.html#CB_forceString">CB_forceString</a></li><li><a href="global.html#CB_getBase64StringObject">CB_getBase64StringObject</a></li><li><a href="global.html#CB_getCookie">CB_getCookie</a></li><li><a href="global.html#CB_getDatum">CB_getDatum</a></li><li><a href="global.html#CB_getJSONPropertyValue">CB_getJSONPropertyValue</a></li><li><a href="global.html#CB_getLZStringObject">CB_getLZStringObject</a></li><li><a href="global.html#CB_getValueIndex">CB_getValueIndex</a></li><li><a href="global.html#CB_getValuePath">CB_getValuePath</a></li><li><a href="global.html#CB_includeJSFile">CB_includeJSFile</a></li><li><a href="global.html#CB_indexOf">CB_indexOf</a></li><li><a href="global.html#CB_init">CB_init</a></li><li><a href="global.html#CB_intToBase">CB_intToBase</a></li><li><a href="global.html#CB_isArray">CB_isArray</a></li><li><a href="global.html#CB_isEmail">CB_isEmail</a></li><li><a href="global.html#CB_isFileLocal">CB_isFileLocal</a></li><li><a href="global.html#CB_isString">CB_isString</a></li><li><a href="global.html#CB_lastIndexOf">CB_lastIndexOf</a></li><li><a href="global.html#CB_ltrim">CB_ltrim</a></li><li><a href="global.html#CB_nl2br">CB_nl2br</a></li><li><a href="global.html#CB_nlToBr">CB_nlToBr</a></li><li><a href="global.html#CB_numberFormat">CB_numberFormat</a></li><li><a href="global.html#CB_numberOfDecimalDigits">CB_numberOfDecimalDigits</a></li><li><a href="global.html#CB_numberOfDecimals">CB_numberOfDecimals</a></li><li><a href="global.html#CB_numberOfIntegerDigits">CB_numberOfIntegerDigits</a></li><li><a href="global.html#CB_parseJSON">CB_parseJSON</a></li><li><a href="global.html#CB_parseString">CB_parseString</a></li><li><a href="global.html#CB_regularExpressionString">CB_regularExpressionString</a></li><li><a href="global.html#CB_renderString">CB_renderString</a></li><li><a href="global.html#CB_replaceAll">CB_replaceAll</a></li><li><a href="global.html#CB_rtrim">CB_rtrim</a></li><li><a href="global.html#CB_scriptPath">CB_scriptPath</a></li><li><a href="global.html#CB_scriptPathCalculate">CB_scriptPathCalculate</a></li><li><a href="global.html#CB_setCookie">CB_setCookie</a></li><li><a href="global.html#CB_setDatum">CB_setDatum</a></li><li><a href="global.html#CB_sizeOf">CB_sizeOf</a></li><li><a href="global.html#CB_sizeof">CB_sizeof</a></li><li><a href="global.html#CB_stringifyJSON">CB_stringifyJSON</a></li><li><a href="global.html#CB_symmetricCall">CB_symmetricCall</a></li><li><a href="global.html#CB_symmetricCallClear">CB_symmetricCallClear</a></li><li><a href="global.html#CB_this">CB_this</a></li><li><a href="global.html#CB_trim">CB_trim</a></li>
51 </ul>
52 </li>
53
54 </ul>
55
56 <div class="col-sm-3 col-md-3">
57 <form class="navbar-form" role="search">
58 <div class="input-group">
59 <input type="text" class="form-control" placeholder="Search" name="q" id="search-input">
60 <div class="input-group-btn">
61 <button class="btn btn-default" id="search-submit"><i class="glyphicon glyphicon-search"></i></button>
62 </div>
63 </div>
64 </form>
65 </div>
66
67 </div>
68
69</div>
70</div>
71
72
73<div class="container" id="toc-content" style="width:100%;">
74<div class="row" style="width:100%;">
75
76
77 <div class="col-md-12">
78
79 <div id="main">
80
81
82 <h1 class="page-title">Source: CrossBase/audiovisual/audio/CB_AudioFile_API_WAAPI.js</h1>
83
84<section>
85 <article>
86 <pre
87 class="sunlight-highlight-javascript linenums">/**
88 * @file Audio files management using "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}). Contains the {@link CB_AudioFile_API.WAAPI} class.
89 * @author Joan Alba Maldonado &lt;workindalian@gmail.com>
90 * @license Creative Commons Attribution 4.0 International. See more at {@link https://crossbrowdy.com/about#what_is_the_crossbrowdy_copyright_and_license}.
91 */
92
93
94//var CB_AudioFile_WAAPI_filePathsLoading = []; //Stores the file paths which are being loaded.
95//var CB_AudioFile_WAAPI_BuffersCache = {}; //Cache of buffers for every file path.
96//var CB_AudioFile_WAAPI_BuffersCacheMessage = {}; //Cache of buffers for every file path.
97//var CB_AudioFile_WAAPI_CheckedCache = {}; //Cache of results of checkPlaying method for every file path.
98
99//We need a limit to prevent out of memory error when many calls to play() are performed:
100var CB_AudioFile_API_WAAPI_beingLoading = 0; //Counts how many objects are loading.
101var CB_AudioFile_API_WAAPI_maximumLoading = 3; //Maximum of objects that can be loading at the same time.
102var CB_AudioFile_API_WAAPI_beingChecking = 0; //Counts how many objects are loading.
103var CB_AudioFile_API_WAAPI_maximumChecking = 75; //Maximum of objects that can be loading at the same time.
104
105
106//Class to manage an audio file with WAAPI (Web Audio API):
107if (typeof(CB_AudioFile_API) === "undefined") { var CB_AudioFile_API = {}; }
108/**
109 * The constructor is recommended to be called through a user-driven event (as onClick, onTouch, etc.) if the "autoPlay" option is set to true, as some web clients may need this at least the first time in order to be able to play the audio.
110 * @class CB_AudioFile_API.WAAPI
111 * @memberof! &lt;global>
112 * @classdesc Class to manage an audio file using "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}). Used by the {@link CB_AudioFile} class internally and it shares most of its properties and methods. Recommended for internal usage only. Uses [Base64Binary]{@link https://gist.github.com/htchaan/108b7aa6b71eb03e38019e64450ea095} internally. Some old clients can use this audio API thanks to [AudioContext-MonkeyPatch]{@link https://github.com/cwilso/AudioContext-MonkeyPatch} and [WAAPISim]{@link https://github.com/g200kg/WAAPISim}.
113 * @param {string} filePath - The path of the audio file or a data URI. NOTE: Only some clients with some audio APIs will support data URIs.
114 * @param {string} [audioId='CB_AUDIOFILE_WAAPI_' + CB_AudioFile_API.WAAPI._idUnique++] - Desired identifier for the audio object. If not provided, an automatic unique ID will be calculated. Note that it is not case sensitive and it should be unique for each object.
115 * @param {CB_AudioFile_API.WAAPI.OPTIONS} [options=CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS] - Object with the desired options.
116 * @param {function} [callbackOk] - Function with no parameters to be called when the audio has been loaded successfully, being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
117 * @param {function} [callbackError] - Function to be called if the audio has not been loaded successfully. The first and unique parameter will be a string describing the error found (if could be determined), being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
118 * @returns {CB_AudioFile_API.WAAPI} Returns a new {@link CB_AudioFile_API.WAAPI} object.
119 * @todo Do not allow to create one object with an "id" which has already been used (unless the value is undefined, null...). Note that the "id" is not case sensitive and it should be unique for each object.
120 * @todo Method getCopy and static method filterProperties (similar to the ones from {@link CB_GraphicSprites} and {@link CB_GraphicSpritesScene}).
121 */
122CB_AudioFile_API["WAAPI"] = function(filePath, audioId, options, callbackOk, callbackError)
123{
124 //Creates an instance of this object and returns it in the case that it is being called from an unexpected context:
125 if (this === window || !(this instanceof CB_AudioFile_API["WAAPI"])) { return new CB_AudioFile_API["WAAPI"](filePath, audioId, options, callbackOk, callbackError); }
126
127 //Constants:
128 /**
129 * Keeps the default volume. If the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT} property is true, this will keep the result of calling the {@link CB_Speaker.getVolume} function. Otherwise, it will use the value of the {@link CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME} variable.
130 * @constant CB_AudioFile_API.WAAPI#DEFAULT_VOLUME
131 * @type {number}
132 * @default CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT ? CB_Speaker.getVolume() : CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME
133 */
134 CB_AudioFile_API["WAAPI"].prototype.DEFAULT_VOLUME = CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT ? CB_Speaker.getVolume() : CB_Configuration[CB_BASE_NAME].CB_Speaker_DEFAULT_VOLUME;
135
136 /**
137 * Keeps the default options when an object is created. Format: { autoLoad: boolean, autoPlay: boolean, loop: boolean, volume: number }.
138 * @constant CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS
139 * @type {CB_AudioFile_API.WAAPI.OPTIONS}
140 * @default { autoLoad: true, autoPlay: false, loop: false, volume: [CB_AudioFile_API.WAAPI.prototype.DEFAULT_VOLUME]{@link CB_AudioFile_API.WAAPI#DEFAULT_VOLUME} }
141 */
142 CB_AudioFile_API["WAAPI"].prototype.DEFAULT_OPTIONS = { autoLoad: true, autoPlay: false, loop: false, volume: CB_AudioFile_API["WAAPI"].prototype.DEFAULT_VOLUME, useXHR: true, useCache: true }; //Default options when the file is created.
143
144 //Properties and variables:
145 /**
146 * Tells whether the file is unloaded ({@link CB_AudioFile.UNLOADED}), loading ({@link CB_AudioFile.LOADING}), unchecked ({@link CB_AudioFile.UNCHECKED}), checking ({@link CB_AudioFile.CHECKING}), loaded ({@link CB_AudioFile.LOADED}), failed ({@link CB_AudioFile.FAILED}) or aborted ({@link CB_AudioFile.ABORTED}).
147 * @var CB_AudioFile_API.WAAPI#status
148 * @readonly
149 * @type {integer}
150 * @default {@link CB_AudioFile.UNLOADED}
151 */
152 this.status = CB_AudioFile.UNLOADED;
153
154 /**
155 * Defines whether the file loops by default when the audio is played or not. Its value will be modified automatically whenever the {@link CB_AudioFile_API.WAAPI#play} method is called, getting the value from the "loop" parameter (but only if contains a boolean).
156 * @var CB_AudioFile_API.WAAPI#loop
157 * @readonly
158 * @type {boolean}
159 * @default [CB_AudioFile_API.WAAPI.prototype.DEFAULT_OPTIONS]{@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.loop
160 */
161 this.loop = CB_AudioFile_API["WAAPI"].prototype.DEFAULT_OPTIONS.loop;
162
163 /**
164 * Stores the volume of this audio. Accepted values go from 0 to MAX_VOLUME, where MAX_VOLUME is 100 if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is false or otherwise MAX_VOLUME is the returning value of the {@link CB_Speaker.getVolume} function.
165 * @var CB_AudioFile_API.WAAPI#volume
166 * @readonly
167 * @type {number}
168 * @default [CB_AudioFile_API.WAAPI.prototype.DEFAULT_OPTIONS]{@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.volume
169 */
170 this.volume = CB_AudioFile_API["WAAPI"].prototype.DEFAULT_OPTIONS.volume;
171
172 /**
173 * Stores the volume of this audio before it was muted (to restore it later). Valid values go from 0 to MAX_VOLUME, where MAX_VOLUME is 100 if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is false or otherwise MAX_VOLUME is the returning value of the {@link CB_Speaker.getVolume} function.
174 * @var CB_AudioFile_API.WAAPI#volumeBeforeMute
175 * @readonly
176 * @type {number}
177 * @default {@link CB_AudioFile_API.WAAPI#volume}
178 */
179 this.volumeBeforeMute = this.volume;
180
181 /**
182 * Stores the identifier for the audio file.
183 * @var CB_AudioFile_API.WAAPI#id
184 * @readonly
185 * @type {string}
186 * @default 'CB_AUDIOFILE_WAAPI_' + CB_AudioFile_API.WAAPI._idUnique++
187 */
188 this.id = "";
189
190 /**
191 * Stores the path of the audio file or the data URI. NOTE: Only some clients with some audio APIs will support data URIs.
192 * @var CB_AudioFile_API.WAAPI#filePath
193 * @readonly
194 * @type {string}
195 * @default
196 */
197 this.filePath = "";
198
199 /**
200 * Tells whether the audio is paused or not.
201 * @var CB_AudioFile_API.WAAPI#paused
202 * @readonly
203 * @type {boolean}
204 * @default false
205 */
206 this.paused = false;
207
208 /**
209 * Stores the time (in milliseconds) when the audio has been paused.
210 * @var CB_AudioFile_API.WAAPI#pauseTime
211 * @readonly
212 * @type {number}
213 * @default
214 */
215 this.pauseTime = 0;
216
217 /**
218 * Tells whether the audio is stopped or not.
219 * @var CB_AudioFile_API.WAAPI#stopped
220 * @readonly
221 * @type {boolean}
222 * @default true
223 */
224 this.stopped = true;
225
226 /**
227 * Function to call when the audio stops.
228 * @var CB_AudioFile_API.WAAPI#onStopFunction
229 * @readonly
230 * @type {function}
231 * @default
232 */
233 this.onStopFunction = null;
234
235 /**
236 * Stores the last "startAt" parameter value used by the {@link CB_AudioFile_API.WAAPI#play} or the {@link CB_AudioFile_API.WAAPI#resume} method.
237 * @var CB_AudioFile_API.WAAPI#lastStartAt
238 * @readonly
239 * @type {number}
240 * @default
241 */
242 this.lastStartAt = null;
243
244 /**
245 * Stores the last "stopAt" parameter value used by the {@link CB_AudioFile_API.WAAPI#play} or the {@link CB_AudioFile_API.WAAPI#resume} method.
246 * @var CB_AudioFile_API.WAAPI#lastStopAt
247 * @readonly
248 * @type {number}
249 * @default
250 */
251 this.lastStopAt = null;
252
253 /**
254 * Stores the "source" ([AudioBufferSourceNode]{@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode} object) of the audio, used by the "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}).
255 * @var CB_AudioFile_API.WAAPI#source
256 * @readonly
257 * @type {AudioBufferSourceNode}
258 * @default
259 */
260 this.source = null;
261
262 /**
263 * Stores the "buffer" ([AudioBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBufferSourceNode/buffer} object) of the audio, used by the "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}).
264 * @var CB_AudioFile_API.WAAPI#buffer
265 * @readonly
266 * @type {AudioBuffer}
267 * @default
268 */
269 this.buffer = null;
270
271
272 /**
273 * Stores the "gain node" ([GainNode]{@link https://developer.mozilla.org/en-US/docs/Web/API/GainNode} object created with the [createGain]{@link https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createGain} method) of the audio, used by the "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}).
274 * @var CB_AudioFile_API.WAAPI#gainNode
275 * @readonly
276 * @type {GainNode}
277 * @default
278 */
279 this.gainNode = null;
280
281 /**
282 * Progress of the loading process (or downloading through [XHR]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/XMLHttpRequest}) the audio data, used by the "WAAPI" ([HTML5 Web Audio API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API}). Internal usage only recommended (use the {@link CB_AudioFile_API.WAAPI#getProgress} method instead to know the progress).
283 * @var CB_AudioFile_API.WAAPI#progressDownloading
284 * @readonly
285 * @type {number}
286 * @default 0
287 */
288 this.progressDownloading = 0;
289
290
291 //Internal properties:
292 this._startTime = 0; //Stores the time when the play starts (used for WAAPI).
293 this._timeoutWhenStop = null; //Keeps the timeout that is executed when the audio has finished playing (to either stop or loop).
294 this._id_internal = null; //Internal id.
295 this._recursiveCallTimeout = null;
296 this._checkCurrentTimeChangesTimeout = null;
297 this._callbackFunctionOkTimeout = null;
298 this._loadingCounterIncreased = false;
299 this._checkPlayingFinishingTimeout = null;
300 this._recursiveCallCheckingTimeout = null;
301 this._checkingCounterIncreased = false;
302 this._lastDuration = null;
303
304
305 //Calls the constructor of the object when creates an instance:
306 return this._init(filePath, audioId, options, callbackOk, callbackError);
307}
308
309
310/**
311 * Object with the options for an audio file. The format is the following one: { autoLoad: boolean, autoPlay: boolean, loop: boolean, volume: number }.
312 * @memberof CB_AudioFile_API.WAAPI
313 * @typedef {Object} CB_AudioFile_API.WAAPI.OPTIONS
314 * @property {boolean} [autoLoad={@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.autoLoad] - If set to false, it will not call the {@link CB_AudioFile_API.WAAPI#load} method internally when the constructor is called (not recommended).
315 * @property {boolean} [autoPlay={@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.autoPlay] - Value which will be used as the "autoPlay" parameter when calling the {@link CB_AudioFile_API.WAAPI#load} method internally, only when the "autoLoad" is set to true (when the constructor is called).
316 * @property {boolean} [loop={@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.loop] - Value that will be used for the {@link CB_AudioFile_API.WAAPI#loop} property.
317 * @property {number} [volume={@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.volume] - The desired volume (from 0 to the maximum value, where the maximum value will be the returning value of calling the {@link CB_Speaker.getVolume} function if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is set to true or it will be 100 otherwise) that will be used for the {@link CB_AudioFile_API.WAAPI#volume} property.
318 */
319
320
321//Static properties:
322CB_AudioFile_API["WAAPI"]._counter = 0; //Internal counter.
323CB_AudioFile_API["WAAPI"]._idUnique = 0; //Counter to make the id unique.
324CB_AudioFile_API["WAAPI"]._cache = [];
325CB_AudioFile_API["WAAPI"].audioContext = null;
326
327
328//Constructor:
329CB_AudioFile_API["WAAPI"].prototype._init = function(filePath, audioId, options, callbackOk, callbackError)
330{
331 //If not given, defines the default parameters:
332 if (typeof(audioId) === "undefined" || audioId === null) { audioId = "CB_AUDIOFILE_WAAPI_" + CB_AudioFile_API["WAAPI"]._idUnique++; } //Uses the file path as default id.
333 if (typeof(options) === "undefined" || options === null) { options = this.DEFAULT_OPTIONS; }
334 else
335 {
336 if (typeof(options.loop) === "undefined" || options.loop === null) { options.loop = this.DEFAULT_OPTIONS.loop; }
337 if (typeof(options.autoLoad) === "undefined" || options.autoLoad === null) { options.autoLoad = this.DEFAULT_OPTIONS.autoLoad; }
338 if (typeof(options.autoPlay) === "undefined" || options.autoPlay === null) { options.autoPlay = this.DEFAULT_OPTIONS.autoPlay; }
339 if (typeof(options.volume) === "undefined" || options.volume === null) { options.volume = this.DEFAULT_OPTIONS.volume; }
340 if (typeof(options.useXHR) === "undefined" || options.useXHR === null) { options.useXHR = this.DEFAULT_OPTIONS.useXHR; }
341 if (typeof(options.useCache) === "undefined" || options.useCache === null) { options.useCache = this.DEFAULT_OPTIONS.useCache; }
342 }
343
344 //Sets the audio ID:
345 this.id = CB_trim(audioId).toUpperCase();
346
347 //Sets the internal id:
348 if (typeof(this._id_internal) === "undefined" || this._id_internal === null) { this._id_internal = CB_AudioFile_API["WAAPI"]._counter++; }
349
350 //Sets the file path:
351 this.filePath = filePath;
352
353 //Proceeds according to the options sent:
354 this.loop = options.loop;
355 this.volume = options.volume;
356 this.volumeBeforeMute = this.volume;
357 if (options.autoLoad)
358 {
359 var that = this;
360 setTimeout
361 (
362 function()
363 {
364 that.load(that.filePath, options.autoPlay, callbackOk, callbackError, null, options.useXHR, options.useCache);
365 },
366 10
367 );
368 }
369
370 //Returns the object:
371 return this;
372}
373
374
375/**
376 * Destroys the audio file object and frees memory. Sets its current {@link CB_AudioFile_API.WAAPI#status} property to ABORTED ({@link CB_AudioFile.ABORTED} value).
377 * @function CB_AudioFile_API.WAAPI#destructor
378 * @param {boolean} [stopSound=false] - If set to true, it will also call the {@link CB_AudioFile_API.WAAPI#stop} method.
379 * @param {boolean} [keepStoppedUnaltered=false] - Used internally as the "keepStoppedUnaltered" parameter to call the {@link CB_AudioFile_API.WAAPI#stop} method. If the "stopSound" parameter is not set to true, this parameter will be ignored as the "stop" method will not be called.
380 * @param {boolean} [avoidOnStop=false] - Used internally as the "avoidOnStop" parameter to call the {@link CB_AudioFile_API.WAAPI#stop} method. If the "stopSound" parameter is not set to true, this parameter will be ignored as the "stop" method will not be called.
381 * @param {boolean} [forceOnStop=false] - Used internally as the "forceOnStop" parameter to call the {@link CB_AudioFile_API.WAAPI#stop} method. If the "stopSound" parameter is not set to true, this parameter will be ignored as the "stop" method will not be called.
382 */
383CB_AudioFile_API["WAAPI"].prototype.destructor = function(stopSound, keepStoppedUnaltered, avoidOnStop, forceOnStop)
384{
385 this._lastDuration = null;
386 //if (typeof(this.audioObject) === "undefined" || this.audioObject === null) { this.status = CB_AudioFile.ABORTED; return; }
387 if (stopSound) { this.stop(keepStoppedUnaltered, avoidOnStop, forceOnStop); }
388 CB_Elements.remove(this.source);
389 CB_Elements.remove(this.buffer);
390 CB_Elements.remove(this.gainNode);
391 //CB_Elements.remove(this.audioObject);
392
393 //if (this.status === CB_AudioFile.LOADING)
394 if (this._loadingCounterIncreased)
395 {
396 this._loadingCounterIncreased = false;
397 CB_AudioFile_API_WAAPI_beingLoading--; //Decreases the counter of the objects which are loading.
398 if (CB_AudioFile_API_WAAPI_beingLoading &lt; 0) { CB_AudioFile_API_WAAPI_beingLoading = 0; }
399 }
400
401 if (this._checkingCounterIncreased)
402 {
403 this._checkingCounterIncreased = false;
404 CB_AudioFile_API_WAAPI_beingChecking--; //Decreases the counter of the objects which are checking.
405 if (CB_AudioFile_API_WAAPI_beingChecking &lt; 0) { CB_AudioFile_API_WAAPI_beingChecking = 0; }
406 }
407
408 this.status = CB_AudioFile.ABORTED;
409}
410
411
412//Returns index of the cache of a given filepath or creates a new slot for it:
413CB_AudioFile_API["WAAPI"].prototype._getCacheIndex = function(filePath)
414{
415 var index = -1;
416
417 var cacheLength = CB_AudioFile_API["WAAPI"]._cache.length;
418 for (var x = 0; x &lt; cacheLength; x++)
419 {
420 if (CB_AudioFile_API["WAAPI"]._cache[x].filePath === filePath)
421 {
422 index = x;
423 break;
424 }
425 }
426
427 if (index === -1)
428 {
429 index = CB_AudioFile_API["WAAPI"]._cache.length;
430 CB_AudioFile_API["WAAPI"]._cache[index] = [];
431 CB_AudioFile_API["WAAPI"]._cache[index].filePath = filePath;
432 CB_AudioFile_API["WAAPI"]._cache[index].loading = false;
433 CB_AudioFile_API["WAAPI"]._cache[index].buffer = null;
434 CB_AudioFile_API["WAAPI"]._cache[index].error = null;
435 CB_AudioFile_API["WAAPI"]._cache[index].checkResult = null;
436 }
437
438 return index;
439}
440
441
442/**
443 * Loads the desired audio file with the desired options. Recommended to be called through a user-driven event (as onClick, onTouch, etc.), as some web clients may need this at least the first time in order to be able to play the audio. Uses [Base64Binary]{@link https://gist.github.com/htchaan/108b7aa6b71eb03e38019e64450ea095} internally. This method will be called automatically by the constructor if the "autoLoad" option was set to true in its given "options" parameter.
444 * When this method is called, if the {@link CB_AudioFile_API.WAAPI#status} property already has the "LOADED" status (defined in the {@link CB_AudioFile.LOADED} constant) and the "forceReload" parameter is not set to true, it will exit calling the given "callbackOk" function (if any) immediately. Otherwise, regardless the status, the status will be set to "LOADING" (defined in the {@link CB_AudioFile.LOADING} constant). After it, it will reach the "UNCHECKED" (defined in the {@link CB_AudioFile.UNCHECKED} constant). If the "autoPlay" parameter is not set to true, this will be the final status (and it will be necessary to call the {@link CB_AudioFile_API.WAAPI#checkPlaying} method after it). After it and only if the "autoPlay" is set to true, as the {@link CB_AudioFile_API.WAAPI#checkPlaying} method will be called internally, it will have the "CHECKING" status (defined in the {@link CB_AudioFile.CHECKING} constant) and finally the "LOADED" status (defined in the {@link CB_AudioFile.LOADED} constant) if all goes well.
445 * @function CB_AudioFile_API.WAAPI#load
446 * @param {string} [filePath={@link CB_AudioFile_API.WAAPI#filePath}] - The path of the audio file or a data URI. NOTE: Only some clients with some audio APIs will support data URIs.
447 * @param {string} [autoPlay=false] - If set to true, it will start playing the audio automatically (by calling the {@link CB_AudioFile_API.WAAPI#play} method internally). If set to true and the {@link CB_AudioFile_API.WAAPI#status} property reaches the "UNCHECKED" status (defined in the {@link CB_AudioFile.UNCHECKED} constant), it will also call internally the {@link CB_AudioFile_API.WAAPI#checkPlaying} method.
448 * @param {function} [callbackOk] - Function with no parameters to be called when the audio has been loaded successfully, being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
449 * @param {function} [callbackError] - Function to be called if the audio has not been loaded successfully. The first and unique parameter will be a string describing the error found (if it could be determined), being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
450 * @param {boolean} [forceReload=false] - If set to false, the "filePath" has not been changed from the previously used and the {@link CB_AudioFile_API.WAAPI#status} property belongs to the "LOADED" status (defined in the {@link CB_AudioFile.LOADED} constant), it will exit the method without loading the audio file again (calling the "callbackOk" function, if any).
451 * @param {boolean} [useXHR=[CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS]{@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.useXHR] - Defines whether to use or not [XHR]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/XMLHttpRequest} ([AJAX]{@link https://en.wikipedia.org/wiki/Ajax_(programming)}) to load the audio file.
452 * @param {boolean} [useCache=[CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS]{@link CB_AudioFile_API.WAAPI#DEFAULT_OPTIONS}.useCache] - Defines whether to try to use or not a cache for performance purposes. If set to true and the audio file was loaded before, it will try to use the cache (if possible) to accelerate the loading process.
453 * @returns {CB_AudioFile_API.WAAPI|null} Returns the audio API object (if it was possible to create) or null otherwise.
454 */
455CB_AudioFile_API["WAAPI"].prototype.load = function(filePath, autoPlay, callbackOk, callbackError, forceReload, useXHR, useCache)
456{
457 clearTimeout(this._checkCurrentTimeChangesTimeout);
458 clearTimeout(this._callbackFunctionOkTimeout);
459 clearTimeout(this._recursiveCallTimeout);
460 clearTimeout(this._checkPlayingFinishingTimeout);
461
462 filePath = filePath || this.filePath;
463
464 //If the status is LOADED and the file path give is the same as the current one, just exits:
465 if (!forceReload &amp;&amp; this.status === CB_AudioFile.LOADED &amp;&amp; this.filePath === filePath)
466 {
467 if (typeof(callbackOk) === "function") { callbackOk.call(this); }
468 return this;
469 }
470
471 this.status = CB_AudioFile.LOADING; //The file is loading.
472
473 var that = this;
474
475 if (typeof(useCache) === "undefined" || useCache === null) { useCache = this.DEFAULT_OPTIONS.useCache; }
476
477 var filePathIndex = this._getCacheIndex(filePath);
478
479 //If we do not use cache the maximum of objects loading is reached or we use cache but the file path is loading, calls the function again after some time and exits:
480 //if (!useCache &amp;&amp; CB_AudioFile_API_WAAPI_beingLoading >= CB_AudioFile_API_WAAPI_maximumLoading || useCache &amp;&amp; typeof(CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading) !== "undefined" &amp;&amp; CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading)
481 if (autoPlay &amp;&amp; !useCache &amp;&amp; CB_AudioFile_API_WAAPI_beingLoading >= CB_AudioFile_API_WAAPI_maximumLoading || useCache &amp;&amp; typeof(CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading) !== "undefined" &amp;&amp; CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading)
482 {
483 this._recursiveCallTimeout = setTimeout(function() { that.load(filePath, autoPlay, callbackOk, callbackError, forceReload, useXHR, useCache); }, 100);
484 return this;
485 }
486
487 //If the buffer for this file path is still not in the cache, other files will have to wait until it loads:
488 if (typeof(CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer) === "undefined" || CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer === null)
489 {
490 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = true; //The file path is loading.
491 }
492
493 if (typeof(useXHR) === "undefined" || useXHR === null) { useXHR = this.DEFAULT_OPTIONS.useXHR; }
494
495 //Destroys previous object (if any):
496 this.destructor(true, false, true); //Also stops the sound (if any) and prevents firing onStop.
497
498 this.status = CB_AudioFile.LOADING; //The file is loading.
499
500 if (!this._loadingCounterIncreased)
501 {
502 this._loadingCounterIncreased = true;
503 CB_AudioFile_API_WAAPI_beingLoading++; //Increases the counter of the objects which are loading (destructor has decreased 1).
504 }
505
506 this.filePath = filePath;
507
508 //Callback wrapper function when an error happens:
509 var callbackFunctionError =
510 function(error, failedChecking, avoidRegisteringError)
511 {
512 if (that.status === CB_AudioFile.ABORTED) { return; } //If it is has been aborted, we exit.
513
514 if (filePath.substring(0, 5).toLowerCase() === "data:") { filePath = filePath.substring(0, 15) + "[...]" + filePath.substring(filePath.length - 2); }
515 if (typeof(error) === "undefined" || error === null) { error = "Unknown error for " + filePath + " file"; }
516 else if (typeof(error.status) !== "undefined") { error = "XHR request for " + filePath + " file returned " + error.status; }
517 else { error = "Error message for " + filePath + " file: " + error; }
518
519 //Stores as failed (-1) in the cache (for the next time if any CB_AudioFile_API["WAAPI"] object wants to use cache for the same file path):
520 if (!failedChecking) //We do not store in the cache as it has failed if it has failed checking (because maybe the file is fine but just failed checking).
521 {
522 if (!avoidRegisteringError) { CB_AudioFile_API["WAAPI"]._cache[filePathIndex].error = error; }
523 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer = -1;
524 }
525
526 if (that._loadingCounterIncreased)
527 {
528 that._loadingCounterIncreased = false;
529 CB_AudioFile_API_WAAPI_beingLoading--; //Decreases the counter of the objects which are loading.
530 if (CB_AudioFile_API_WAAPI_beingLoading &lt; 0) { CB_AudioFile_API_WAAPI_beingLoading = 0; }
531 }
532
533 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = false; //The file path is not loading anymore.
534
535 that.status = CB_AudioFile.FAILED; //File failed to load.
536 autoPlay = false;
537 //var fileName = filePath;
538
539 if (typeof(callbackError) === "function") { callbackError.call(that, error); } //Calls the Error function back.
540 };
541
542 //Callback wrapper function when all goes well:
543 var callbackFunctionOk =
544 function()
545 {
546 //Function to execute when all is OK:
547 var allIsFine =
548 function()
549 {
550 if (that.status === CB_AudioFile.ABORTED) { return; } //If it is has been aborted, we exit.
551
552 //Stores the buffer in the cache (for the next time if any CB_AudioFile_API["WAAPI"] object wants to use cache for the same file path):
553 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer = that.buffer;
554
555 if (that._loadingCounterIncreased)
556 {
557 that._loadingCounterIncreased = false;
558 CB_AudioFile_API_WAAPI_beingLoading--; //Decreases the counter of the objects which are loading.
559 if (CB_AudioFile_API_WAAPI_beingLoading &lt; 0) { CB_AudioFile_API_WAAPI_beingLoading = 0; }
560 }
561
562 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = false; //The file path is not loading anymore.
563
564 if (typeof(callbackOk) === "function") { callbackOk.call(that); } //Calls the OK function back.
565
566 //Plays automatically if we want to:
567 if (autoPlay) { that.play(); }
568 };
569
570 that.status = CB_AudioFile.UNCHECKED; //The file is still unchecked.
571 //If we want to play automatically, checks if the currentTime changes (some web clients cannot play if the user did not fire an event to call the play function):
572 if (autoPlay)
573 {
574 that.checkPlaying(function() { allIsFine(); }, function(error) { callbackFunctionError(error, true); }, false, false, useCache);
575 }
576 else
577 {
578 //Starts playing and stops immediately the sound (needed for some web clients, as Edge, to be able to play the sound later from the first attempt):
579 try
580 {
581 that.source = CB_AudioFile_API["WAAPI"].audioContext.createBufferSource();
582 var source = that.source;
583 source.buffer = that.buffer;
584 that.gainNode = CB_AudioFile_API["WAAPI"].audioContext.createGain();
585 that.source.connect(that.gainNode);
586 that.gainNode.connect(CB_AudioFile_API["WAAPI"].audioContext.destination);
587 var previousVolume = that.volume;
588 if (CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_MUTE_ON_LOAD_AND_CHECKING)
589 {
590 that.setVolume(0);
591 }
592 source.loop = false;
593 source.start(0, 0);
594 that.source.stop(0);
595 that.setVolume(previousVolume);
596 } catch(E) {}
597
598 //Calls the final function:
599 allIsFine();
600 }
601 }
602
603 try
604 {
605 //Function that decodes the binary data:
606 var callbackFunctionDecode =
607 function(binaryData)
608 {
609 that.progressDownloading = 100; //The file must have been downloaded since we want to decode it now.
610 that.decodeAudioData(binaryData, callbackFunctionOk, callbackFunctionError);
611 };
612
613 if (typeof(window.AudioContext) === "undefined" &amp;&amp; typeof(window.webkitAudioContext) === "undefined")
614 {
615 callbackFunctionError("Web Audio API not found");
616 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = false; //The file path is not loading.
617 return null;
618 }
619
620 //Just creates an audio context:
621 if (typeof(CB_AudioFile_API["WAAPI"].audioContext) === "undefined" || CB_AudioFile_API["WAAPI"].audioContext === null)
622 {
623 CB_AudioFile_API["WAAPI"].audioContext = new (window.AudioContext || window.webkitAudioContext)();
624 //Hack (source: https://stackoverflow.com/questions/56768576/safari-audiocontext-suspended-even-with-onclick-creation/56770254#56770254):
625 try { CB_AudioFile_API["WAAPI"].audioContext.createGain(); } catch (createGainError) { CB_console("Error creating a GainNode for the AudioContext: " + createGainError); }
626 try { CB_AudioFile_API["WAAPI"].audioContext.resume(); } catch (audioContextResumeError) { CB_console("Error resuming AudioContext: " + audioContextResumeError); }
627 if (!CB_AudioFile_API["WAAPI"].audioContext)
628 {
629 callbackFunctionError("AudioContext/webkitAudioContext object could not be created! Value returned: " + CB_AudioFile_API["WAAPI"].audioContext);
630 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = false; //The file path is not loading.
631 return null;
632 }
633 }
634
635 if (CB_AudioFile_API["WAAPI"].audioContext &amp;&amp; CB_AudioFile_API["WAAPI"].audioContext.state === "suspended")
636 {
637 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = false; //The file path is not loading.
638 try
639 {
640 var readStateHack = CB_AudioFile_API["WAAPI"].audioContext.state; //Hack. Source: https://stackoverflow.com/questions/56768576/safari-audiocontext-suspended-even-with-onclick-creation/56770254#56770254
641 CB_AudioFile_API["WAAPI"].audioContext.resume();
642
643 //CB_AudioFile_API["WAAPI"].audioContext.onstatechange = function()
644 CB_Events.on
645 (
646 CB_AudioFile_API["WAAPI"].audioContext,
647 "statechange",
648 function()
649 {
650 if (CB_AudioFile_API["WAAPI"].audioContext.state === "running")
651 {
652 that.load(filePath, autoPlay, callbackOk, callbackError, forceReload, useXHR, useCache);
653 }
654 },
655 false, //useCapture.
656 true, //keepOldEventFunction.
657 true //erasable.
658 );
659 } catch (resumeOrOnStateChangeError) { CB_console("Error resuming or managing 'onStateChange' event: " + resumeOrOnStateChangeError); }
660 return this;
661 }
662
663 //If we want to use the cache and the file has already been loaded before, gets the buffer from the cache:
664 if (useCache &amp;&amp; typeof(CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer) !== "undefined" &amp;&amp; CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer !== null)
665 {
666 this.buffer = CB_AudioFile_API["WAAPI"]._cache[filePathIndex].buffer;
667 this.progressDownloading = 100;
668 this._callbackFunctionOkTimeout =
669 setTimeout
670 (
671 (this.buffer === -1)
672 ? function() { callbackFunctionError("File path from the cache already failed before. Previous message: " + CB_AudioFile_API["WAAPI"]._cache[filePathIndex].error, false, true); }
673 : callbackFunctionOk,
674 10
675 );
676 }
677 //If we have received a data URI (base64), we don't need to use AJAX (XHR):
678 else if (filePath.substring(0, 5).toLowerCase() === "data:")
679 {
680 if (filePath.indexOf(";base64") !== -1)
681 {
682 //Note: Base64Binary needs Uint8Array and ArrayBuffer support. But the web client will support it if the Web Audio API is supported.
683 var base64Data = filePath.substring(filePath.indexOf(",") + 1); //We just need the data.
684 this.progressDownloading = 100;
685 var byteArray = Base64Binary.decodeArrayBuffer(base64Data);
686 callbackFunctionDecode(byteArray);
687 }
688 else { callbackFunctionError("Data URI does not contain the ';base64' string"); }
689 }
690 //...otherwise, we use AJAX (XHR) to load the file given:
691 else if (useXHR)
692 {
693 //When the file is loaded, calls the function to set it as ready and load the buffer too:
694 var XHR = CB_Net.XHR.callBinary(filePath, null, null, "arraybuffer", null, function(XHR) { callbackFunctionDecode(XHR.response); }, callbackFunctionError, [200, 206]); //Allows partial content (206 XHR status).
695 this.progressDownloading = 0;
696 XHR.onprogress =
697 function updateProgress (event)
698 {
699 if (event.lengthComputable)
700 {
701 that.progressDownloading = event.loaded / event.total * 100;
702 }
703 };
704 }
705 //...otherwise, we use WAAPI from an Audio object:
706 else
707 {
708 this.progressDownloading = 0;
709 //Function that processes the Audio element:
710 var processAudioElement =
711 function()
712 {
713 if (typeof(audioFileObject) !== "undefined" &amp;&amp; typeof(audioFileObject.audioObject) !== "undefined" &amp;&amp; audioFileObject.audioObject !== null)
714 {
715 try
716 {
717 var source = CB_AudioFile_API["WAAPI"].audioContext.createMediaElementSource(audioFileObject.audioObject);
718 var gainNode = CB_AudioFile_API["WAAPI"].audioContext.createGain();
719 /////source.connect(gainNode);
720 /////gainNode.connect(CB_AudioFile_API["WAAPI"].audioContext.destination);
721
722 analyser = CB_AudioFile_API["WAAPI"].audioContext.createAnalyser();
723 source.connect(analyser);
724 analyser.connect(CB_AudioFile_API["WAAPI"].audioContext.destination);
725 //source.start(0, 0);
726
727 callbackFunctionOk(); //Calls the OK function back.
728 }
729 catch (error) { callbackFunctionError(error); }
730 }
731 else { callbackFunctionError("Audio element is undefined or null"); }
732 };
733 //Creates an Audio object with AAPI:
734 var audioFileObject = new CB_AudioFile_API["AAPI"](filePath, "CB_media_" + this.id, { autoLoad: true, autoPlay: false, loop: false }, processAudioElement, callbackFunctionError);
735 }
736 }
737 catch(E)
738 {
739 callbackFunctionError(E);
740 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].loading = false; //The file path is not loading.
741 return null;
742 }
743
744 //Plays automatically if we want to:
745 //if (autoPlay) { this.play(); }
746 //if (autoPlay) { setTimeout(function() { that.play(); }, 5000); }
747
748 return this;
749}
750
751
752/**
753 * Decodes binary audio data given. Internal usage only recommended.
754 * @function CB_AudioFile_API.WAAPI#decodeAudioData
755 * @param {ArrayBuffer} binaryData - [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer} with the audio data to be decoded.
756 * @param {function} [callbackOk] - Function with no parameters to be called when the audio data has been decoded successfully, being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
757 * @param {function} [callbackError] - Function to be called if the audio data has not been decoded successfully. The first and unique parameter will be a string describing the error found (if it could be determined), being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
758 * @returns {undefined|Promise} Returns the returning value of calling the [BaseAudioContext.decodeAudioData]{@link https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData} function, which returns void (undefined) or a [Promise]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise} (whose methods "then" and "catch" will have already been used internally by this function).
759 */
760CB_AudioFile_API["WAAPI"].prototype.decodeAudioData = function(binaryData, callbackOk, callbackError)
761{
762 var that = this;
763
764 var callbackCalled = false;
765 var callbackDecodedOk =
766 function(buffer)
767 {
768 if (callbackCalled) { return; }
769 callbackCalled = true;
770
771 //In case of error:
772 //if (typeof(buffer) === "undefined" || buffer === null || !buffer)
773 if (!buffer)
774 {
775 if (typeof(callbackError) === "function")
776 {
777 callbackError.call(that, "Buffer is not defined or null or empty");
778 }
779 }
780 //...otherwise, the sound has been loaded correctly:
781 else
782 {
783 that.buffer = buffer; //Stores the buffer in this object.
784 if (typeof(callbackOk) === "function") { callbackOk.call(that); } //Calls the OK function back.
785 }
786 };
787
788 var callbackDecodedError =
789 function(error) //In case of error:
790 {
791 if (callbackCalled) { return; }
792 callbackCalled = true;
793 if (typeof(callbackError) === "function") { callbackError.call(that, error); }
794 };
795
796 var returningValue = CB_AudioFile_API["WAAPI"].audioContext.decodeAudioData(binaryData, callbackDecodedOk, callbackDecodedError);
797
798 if (typeof(returningValue) !== "undefined" &amp;&amp; returningValue !== null &amp;&amp; typeof(returningValue.then) === "function")
799 {
800 returningValue.then(callbackDecodedOk)["catch"](callbackDecodedError);
801 }
802
803 return returningValue;
804}
805
806
807/**
808 * Checks whether the audio can be played or not. Recommended to be called through a user-driven event (as onClick, onTouch, etc.), as some web clients may need this at least the first time in order to be able to play the audio. Also recommended to use before calling the {@link CB_AudioFile_API.WAAPI#play} method the first time. The checking action will only be performed if the value of the {@link CB_AudioFile_API.WAAPI#status} property belongs to the {@link CB_AudioFile.UNCHECKED} or to the {@link CB_AudioFile.CHECKING} value. After checking, if the audio can be played, the {@link CB_AudioFile_API.WAAPI#status} of the object will get the value of {@link CB_AudioFile.LOADED}. Otherwise, if it cannot be played, the {@link CB_AudioFile_API.WAAPI#status} property will get the value of {CB_AudioFile.FAILED}.
809 * @function CB_AudioFile_API.WAAPI#checkPlaying
810 * @param {function} [callbackOk] - Function with no parameters to be called when the audio has been checked successfully, being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
811 * @param {function} [callbackError] - Function to be called if the audio has not been checked successfully. The first and unique parameter will be a string describing the error found (if it could be determined), being "this" the {@link CB_AudioFile_API.WAAPI} object itself.
812 * @param {boolean} [ignoreStatus=false] - If set to false and the {@link CB_AudioFile_API.WAAPI#status} property does not belong neither to the "UNCHECKED" status (defined in the {@link CB_AudioFile.UNCHECKED} constant) nor to the "CHECKING" status (defined in the {@link CB_AudioFile.CHECKING} constant), it will fail calling the "callbackError" function (if any). If set to true, it will try to perform the checking action regardless the status of the audio.
813 * @param {boolean} [ignoreQueue=false] - If set to false and there is already the maximum number of audio files being checked (defined internally), the function will exit and it will call itself automatically again and again until the checking process can be performed (when its queue turn has been reached). This is done for performance purposes.
814 * @param {boolean} [useCache=false] - If set to true (not recommended) and the same audio file was checked previously, it will not perform the checking process again and it will do the same as the previous call.
815 * @returns {boolean} Returns false if the checking could not be performed and failed. If it returns true, it can mean either the checking has been processed successfully or it will fail in the future, so it is recommended to ignore the returning value and use the callback functions instead.
816 */
817CB_AudioFile_API["WAAPI"].prototype.checkPlaying = function(callbackOk, callbackError, ignoreStatus, ignoreQueue, useCache)
818{
819 /////clearTimeout(this._recursiveCallCheckingTimeout);
820
821 if (!ignoreStatus &amp;&amp; this.status !== CB_AudioFile.UNCHECKED &amp;&amp; this.status !== CB_AudioFile.CHECKING)
822 {
823 if (typeof(callbackError) === "function") { callbackError.call(this, "Cannot check if status is not unchecked or checking (status is " + this.status + ")"); }
824 return false;
825 }
826
827 this.status = CB_AudioFile.CHECKING;
828
829 var that = this;
830
831 var filePathIndex = this._getCacheIndex(this.filePath);
832
833 if (useCache &amp;&amp; typeof(CB_AudioFile_API["WAAPI"]._cache[filePathIndex].checkResult) !== "undefined" &amp;&amp; CB_AudioFile_API["WAAPI"]._cache[filePathIndex].checkResult !== null)
834 {
835 if (CB_AudioFile_API["WAAPI"]._cache[filePathIndex].checkResult)
836 {
837 this.status = CB_AudioFile.LOADED;
838 if (typeof(callbackOk) === "function") { callbackOk.call(that); }
839 return true;
840 }
841 else
842 {
843 this.status = CB_AudioFile.FAILED;
844 if (typeof(callbackError) === "function") { callbackError.call(that, "File path from the cache already failed checking before."); }
845 return false;
846 }
847 }
848
849 //If we do not use cache the maximum of objects checking is reached or we use cache but the file path is checking, calls the function again after some time and exits:
850 if (!ignoreQueue &amp;&amp; CB_AudioFile_API_WAAPI_beingChecking >= CB_AudioFile_API_WAAPI_maximumChecking)
851 {
852 this._recursiveCallCheckingTimeout = setTimeout(function() { that.checkPlaying(callbackOk, callbackError, ignoreStatus, useCache); }, 10);
853 return true;
854 }
855
856 //////this.status = CB_AudioFile.CHECKING;
857
858 if (!this._checkingCounterIncreased)
859 {
860 this._checkingCounterIncreased = true;
861 CB_AudioFile_API_WAAPI_beingChecking++; //Increases the counter of the objects which are checking.
862 }
863
864 var previousVolume = this.volume;
865 var finishedChecking =
866 function(ok, error, keepStatus)
867 {
868 //Stops the file:
869 that.source.stop(0);
870
871 //Restores the volume:
872 that._checkPlayingFinishingTimeout = //Timeout to prevent hearing the sound in some web clients.
873 setTimeout
874 (
875 function()
876 {
877 that.setVolume(previousVolume);
878
879 //If the file is ok:
880 if (ok)
881 {
882 if (!keepStatus) { that.status = CB_AudioFile.LOADED; }
883 if (typeof(callbackOk) === "function") { callbackOk.call(that); }
884 }
885 //...otherwise, if the file has failed:
886 else
887 {
888 if (!keepStatus) { that.status = CB_AudioFile.FAILED; }
889 if (typeof(callbackError) === "function") { callbackError.call(that, error); }
890 }
891 },
892 10
893 );
894
895 if (that._checkingCounterIncreased)
896 {
897 that._checkingCounterIncreased = false;
898 CB_AudioFile_API_WAAPI_beingChecking--; //Decreases the counter of the objects which are checking.
899 if (CB_AudioFile_API_WAAPI_beingChecking &lt; 0) { CB_AudioFile_API_WAAPI_beingChecking = 0; }
900 }
901
902 CB_AudioFile_API["WAAPI"]._cache[filePathIndex].checkResult = ok; //Stores the result in the cache.
903 };
904
905
906 try
907 {
908 //Tries to play it silently and checks whether current time has changed. If it has not changed, the file has not been loaded properly.
909 //Note: In Opera with Android sometimes it doesn't throw any error and status is "playing" but currentTime is always zero and no sound can be heard.
910 this.source = CB_AudioFile_API["WAAPI"].audioContext.createBufferSource();
911 var source = this.source;
912 source.buffer = this.buffer;
913 this.gainNode = CB_AudioFile_API["WAAPI"].audioContext.createGain();
914 this.source.connect(this.gainNode);
915 this.gainNode.connect(CB_AudioFile_API["WAAPI"].audioContext.destination);
916 if (CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_MUTE_ON_LOAD_AND_CHECKING)
917 {
918 this.setVolume(0);
919 }
920 source.loop = false;
921 source.start(0, 0);
922 var currentTime = CB_AudioFile_API["WAAPI"].audioContext.currentTime;
923 var timesChecked = 0;
924 clearTimeout(this._checkCurrentTimeChangesTimeout);
925 clearTimeout(this._checkPlayingFinishingTimeout);
926
927 var checkCurrentTimeChanges =
928 function(callbackOk, callbackError)
929 {
930 if (that.status === CB_AudioFile.ABORTED || that.status === CB_AudioFile.FAILED)
931 {
932 finishedChecking(false, "Audio file object is " + (that.status === CB_AudioFile.ABORTED ? "ABORTED" : "FAILED") + ".", true);
933 return;
934 }
935 try
936 {
937 that.status = CB_AudioFile.CHECKING;
938 clearTimeout(that._checkCurrentTimeChangesTimeout);
939 var currentTimeNow = CB_AudioFile_API["WAAPI"].audioContext.currentTime;
940 //We check whther the currentTime has changed or not, until a certain number of times:
941 if (currentTime === currentTimeNow)
942 {
943 if (timesChecked &lt; 1000)
944 {
945 timesChecked++;
946 that._checkCurrentTimeChangesTimeout = setTimeout(function() { checkCurrentTimeChanges(callbackOk, callbackError); }, 1);
947 return;
948 }
949 else
950 {
951 finishedChecking(false, "currentTime does not change (it is " + currentTime + ").");
952 }
953 }
954 else
955 {
956 /////that.status = CB_AudioFile.LOADED; //The file has been loaded.
957 finishedChecking(true);
958 }
959 }
960 catch(E)
961 {
962 finishedChecking(false, E);
963 }
964 };
965 checkCurrentTimeChanges(callbackOk, callbackError);
966 return true;
967 }
968 catch(E)
969 {
970 finishedChecking(false, E);
971 return false;
972 }
973}
974
975
976/**
977 * Tells the duration of the audio (in milliseconds). Note that some clients might not calculate the duration correctly and, in this case, a zero (0) value would be returned.
978 * @function CB_AudioFile_API.WAAPI#getDuration
979 * @returns {number} Returns the duration of the audio (in milliseconds). Note that some clients might not calculate the duration correctly and, in this case, a zero (0) value would be returned.
980 */
981CB_AudioFile_API["WAAPI"].prototype.getDuration = function()
982{
983 var duration;
984
985 if (typeof(this.buffer) !== "undefined" &amp;&amp; this.buffer !== null)
986 {
987 duration = this.buffer.duration * 1000;
988 }
989
990 if (typeof(duration) === "undefined" || duration === null || isNaN(duration) || duration &lt; 0) { duration = 0; }
991
992 return duration;
993}
994
995
996/**
997 * Plays the audio.
998 * @function CB_AudioFile_API.WAAPI#play
999 * @param {number} [startAt=0 | {@link CB_AudioFile_API.WAAPI#lastStartAt} | stopAt] - Time in milliseconds where we want the audio to start at. If not provided or it is not a valid number, it will use zero (0) as default which belongs to the beginning of the audio. If the value provided is greater than the "stopAt" provided, it will use the value set in the {@link CB_AudioFile_API.WAAPI#lastStartAt} property (which belongs to the "startAt" value the last time that this method was called). If, even using the {@link CB_AudioFile_API.WAAPI#lastStartAt} value is still greather than the "stopAt" provided, it will use the same value as the "stopAt" which means it will not play and will stop immediately.
1000 * @param {number} [stopAt={@link CB_AudioFile_API.WAAPI#getDuration}()] - Time in milliseconds where we want the audio to stop at. If not provided or it is not a valid number, it will use the returning value of the {@link CB_AudioFile_API.WAAPI#getDuration} method (which should belong to the total duration of the audio, if it was calculated correctly).
1001 * @param {boolean} [loop={@link CB_AudioFile_API.WAAPI#loop}] - Sets whether we want to play the audio looping (starting again and again) or just play it once. Note that at the end of each loop the "onStop" function defined (through the {@link CB_AudioFile_API.WAAPI#onStop} method) will not be called.
1002 * @param {boolean} [avoidDelayedPlay=false] - If set to false (recommended) and the audio failed previously or was aborted (destroyed), it will try to load it correctly again automatically and play it after that if possible (this can take some time so the audio could start playing after a delay). Otherwise, if set to true and the audio failed or was aborted (destroyed), the audio will not play at all and the "stop" method will be called immediately.
1003 * @param {boolean} [allowedRecursiveDelay={@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_ALLOWED_RECURSIVE_DELAY_DEFAULT}] - The maximum amount of time (in milliseconds) of delay that we accept before start playing the audio. If the amount of time is overcome, the audio will not play at all and the {@link CB_AudioFile_API.WAAPI#stop} method will be called immediately. Used only when the "avoidDelayedPlay" parameter is set to false and the audio needs to be loaded because it failed previously or was aborted (destroyed).
1004 * @param {function} [onPlayStart] - Function to be called when the audio starts playing successfully. The function will be called with the following parameters (in order): "startAt", "stopAt", "startAtNextLoop", "loop", "avoidDelayedPlay", "allowedRecursiveDelay" and "startPlayingTime", being "this" the {@link CB_AudioFile_API.WAAPI} object. If the audio is looping, this will be called only once when the audio starts playing the first time and it will not be called next loops.
1005 * @param {function} [onLoadError] - Function to be called if the audio cannot be played successfully. The first and unique parameter will be a string describing the error found (if it could be determined), being "this" the {@link CB_AudioFile_API.WAAPI} object.
1006 * @param {boolean} [isResume=false] - If set to true (not recommended) and it is a looping audio, the next loop will use the value of the {@link CB_AudioFile_API.WAAPI#lastStartAt} property as the "startAt" parameter when it calls this method again automatically (internally). Recommended for internal usage only.
1007 * @param {boolean} [isLooping=false] - Used to determine whether this method was called automatically again by itself because it is looping the audio. Recommended for internal usage only.
1008 * @param {integer} [startPlayingTime] - Contains the time when the audio should start playing. Recommended for internal usage only.
1009 * @returns {boolean|integer} It returns false if the duration is 0 ("startAt" and "stopAt" are the same number), returns "-1" if the audio cannot be played and an error is detected or returns true otherwise. Note that even when it returns true there can be a non-detectable error and maybe the audio is not played.
1010 */
1011CB_AudioFile_API["WAAPI"].prototype.play = function(startAt, stopAt, loop, avoidDelayedPlay, allowedRecursiveDelay, onPlayStart, onLoadError, isResume, isLooping, startPlayingTime)
1012{
1013 var that = this;
1014
1015 var duration = this.getDuration();
1016
1017 if (typeof(startPlayingTime) === "undefined" || startPlayingTime === null) { startPlayingTime = CB_Device.getTiming(); }
1018
1019 //If the sound is not ready yet, calls the function again but later:
1020 /*if (this.status !== CB_AudioFile.LOADED || this.getDuration() === 0) //Duration must be greater than zero.
1021 {
1022 this.stopped = true;
1023 this.paused = false;
1024 //If it has not failed or aborted, calls the function again but later:
1025 if (this.status !== CB_AudioFile.FAILED &amp;&amp; this.status !== CB_AudioFile.ABORTED) { setTimeout(function() { that.play(startAt, stopAt, loop, allowOverlapping, avoidDelayedPlay, onLoadError, isResume, isLooping); }, 1); }
1026 //...otherwise, if it has failed, sets it as stopped:
1027 //else { this.stopped = true; }
1028 return -1;
1029 }*/
1030 if (this.status !== CB_AudioFile.LOADED || duration === 0) //Duration must be greater than zero.
1031 {
1032 this.stopped = true;
1033 this.paused = false;
1034
1035 //If it has not failed or aborted:
1036 if (this.status !== CB_AudioFile.FAILED &amp;&amp; this.status !== CB_AudioFile.ABORTED) //It must be UNLOADED, LOADING, LOADED, UNCHECKED or CHECKING.
1037 {
1038 //Function that calls the play method recursively (unless the maximum time allowed has expired):
1039 var playLaterFunctionCalled = false;
1040 var playLaterFunction =
1041 function()
1042 {
1043 if (playLaterFunctionCalled) { return; }
1044 playLaterFunctionCalled = true;
1045
1046 //If the recursive delay is not null and is a valid number:
1047 if (typeof(allowedRecursiveDelay) === "undefined" || allowedRecursiveDelay === null || isNaN(allowedRecursiveDelay) || allowedRecursiveDelay &lt; 0)
1048 {
1049 allowedRecursiveDelay = CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_ALLOWED_RECURSIVE_DELAY_DEFAULT; //We use default value.
1050 }
1051 var timeNow = CB_Device.getTiming();
1052
1053 //If the time expired is less or equal to the delay allowed:
1054 if (timeNow - startPlayingTime &lt;= allowedRecursiveDelay)
1055 {
1056 //Calls play method again:
1057 that.play(startAt, stopAt, loop, avoidDelayedPlay, allowedRecursiveDelay, onPlayStart, onLoadError, isResume, isLooping, startPlayingTime);
1058 }
1059 //...otherwise, just stops the sound:
1060 /////else { that.stop(false, false); } //Sets as stopped and fires onStop function (if any).
1061 else { that.stop(false, false, true); } //Sets as stopped and fires onStop function (if any).
1062 };
1063
1064 //Function to execute when the sound loads successfully (or finishes checking successfully):
1065 var onLoad =
1066 function()
1067 {
1068 //If we allow delayed play, plays the sound:
1069 if (!avoidDelayedPlay) { playLaterFunction(); }
1070 //...otherwise, just stops the sound (to fire onStop function):
1071 else { that.stop(false, false, true); } //Sets as stopped and fires onStop function (if any).
1072 };
1073
1074 //If it is UNLOADED or we had a duration before but not now and it is not LOADING, loads the file again:
1075 if (this.status === CB_AudioFile.UNLOADED || this.status !== CB_AudioFile.LOADING &amp;&amp; this._lastDuration !== null &amp;&amp; duration === 0)
1076 {
1077 this.load(this.filePath, false, onLoad, onLoadError, true);
1078 }
1079 //...otherwise, if it is UNCHECKED, we call the checking function:
1080 else if (this.status === CB_AudioFile.UNCHECKED)
1081 {
1082 this.checkPlaying(onLoad, onLoadError, false, true, false);
1083 }
1084 //...otherwise, if it is not CHECKING (it must be LOADING or LOADED with duration 0 from the beginning), we will not reload the sound:
1085 else if (this.status !== CB_AudioFile.CHECKING)
1086 {
1087 //If we allow delayed play, calls the play method again but after some time:
1088 if (!avoidDelayedPlay) { setTimeout(playLaterFunction, 1); }
1089 //...otherwise, just stops the sound (to fire onStop function):
1090 /////else { this.stop(false, false); } //Sets as stopped and fires onStop function (if any).
1091 else { this.stop(false, false, true); } //Sets as stopped and fires onStop function (if any).
1092 }
1093 }
1094 return -1;
1095 }
1096
1097 this._lastDuration = duration;
1098
1099 //Defines the default parameters:
1100 if (CB_trim(startAt) === "") { startAt = 0; } //Starts at the beginning as default.
1101 if (CB_trim(stopAt) === "") { stopAt = 0; } //If it is not a number, default is zero.
1102 if (typeof(loop) === "undefined" || loop === null) { loop = this.loop; } //If not set, uses the default (or last one used).
1103 else { this.loop = loop; } //...otherwise, stores the new setting given.
1104
1105 //Sanitizes startAt and stopAt:
1106 startAt = parseFloat(startAt);
1107 stopAt = parseFloat(stopAt);
1108 if (startAt &lt; 0) { startAt = 0; }
1109 if (stopAt &lt;= 0 || stopAt > duration) { stopAt = duration; } //If the stopAt is not correct, plays until the end of the file.
1110 if (startAt > stopAt) { startAt = this.lastStartAt; } //In the case start time is greater than the stop time, starts as the previous time.
1111
1112 if (startAt > stopAt || isNaN(startAt)) { startAt = stopAt; }
1113
1114 //If the duration is zero (startAt and stopAt are equal), exits:
1115 if (startAt === stopAt) { this.stop(); return false; }
1116
1117 //Next loop (if any) it will start at the same time by default:
1118 var startAtNextLoop = startAt;
1119 //If it is a resume, next loop we should start from the previous startAt used:
1120 if (isResume) { startAtNextLoop = this.lastStartAt; }
1121 //...otherwise, if it is not a resume, stores the startAt used:
1122 else { this.lastStartAt = startAt; }
1123 this.lastStopAt = stopAt; //Stores stopAt used.
1124
1125 //Adds the event to check when the file reaches the stop time:
1126 var whenStopFunction =
1127 function()
1128 {
1129 //Removes the event and timeout:
1130 clearTimeout(that._timeoutWhenStop); //Clears the previous timeout.
1131 //CB_Events.remove(source, "ended", whenStopFunction, false);
1132 //CB_Events.remove(source, "webkitended", whenStopFunction, false);
1133
1134 //If the sound has been stopped or paused or the stop time has changed, exits:
1135 if (that.stopped || that.paused || that.lastStopAt !== stopAt) { return; }
1136 //...otherwise, if the stop time has not been reached yet, calls the function again after a while:
1137 ///////else if (that.getCurrentTime() &lt; stopAt) { setTimeout(whenStopFunction, 1); return; }
1138
1139 //If we want to loop, loops again:
1140 if (that.loop)
1141 {
1142 //that.stop(true); //Stops the sound without setting its property as stopped.
1143 that.play(startAtNextLoop, stopAt, true, avoidDelayedPlay, allowedRecursiveDelay, onPlayStart, onLoadError, false, true); //Plays again the sound.
1144 }
1145 //...otherwise, if we don't want to loop, we stop:
1146 else { that.stop(); }
1147 };
1148
1149 //Clears the previous timeout (if any):
1150 clearTimeout(this._timeoutWhenStop);
1151
1152 //If it is not a loop (it is the first call to the play method) or we do not want to loop:
1153 if (!isLooping || !loop) { CB_symmetricCallClear("WAAPI_AUDIO_FILE" + this._id_internal); } //We clean the cache of setTimeoutSynchronized for the loop function.
1154
1155 //If it is looping or does not allow overlapping and it is not paused, stops the possible previous sound:
1156 //if (isLooping || !allowOverlapping &amp;&amp; !this.paused) { this.stop(true); } //Stops the sound without setting its property as stopped.
1157 this.stop(true, true); //Stops the sound without setting its property as stopped.
1158
1159 //Prepares the mystical stuff:
1160 this.source = CB_AudioFile_API["WAAPI"].audioContext.createBufferSource();
1161 var source = this.source;
1162 source.buffer = this.buffer;
1163
1164 //Creates a gain node to be able to set the volume later:
1165 this.gainNode = CB_AudioFile_API["WAAPI"].audioContext.createGain();
1166 this.source.connect(this.gainNode);
1167 this.gainNode.connect(CB_AudioFile_API["WAAPI"].audioContext.destination);
1168
1169 //Applies the current volume:
1170 this.setVolume(this.volume);
1171
1172 //Plays the sound:
1173 source.loop = false; //We will use our own way to loop, so we don't need the normal way.
1174 source.start(0, startAt / 1000, (stopAt - startAt) / 1000); //WAAPI needs seconds.
1175
1176 //Stores the start time (useful for pause/resume):
1177 this._startTime = CB_AudioFile_API["WAAPI"].audioContext.currentTime - (startAt / 1000); //WAAPI needs seconds.
1178
1179 //Sets the event and timeout for when the sound finishes:
1180 //if (typeof(source.onended) !== "undefined") { CB_Events.add(source, "ended", whenStopFunction, false, true, true); }
1181 //else if (typeof(source.webkitended) !== "undefined") { CB_Events.add(source, "webkitended", whenStopFunction, false, true, true); }
1182 //else
1183 //{
1184 var msToFinish = stopAt - startAt;
1185 this._timeoutWhenStop = CB_symmetricCall(whenStopFunction, msToFinish, "WAAPI_AUDIO_FILE" + this._id_internal);
1186 //}
1187
1188 //The sound is neither paused nor stopped:
1189 this.paused = this.stopped = false;
1190
1191 //If it is the first time (not a loop) and there is a function to call when the play starts, we call it:
1192 if (!isLooping &amp;&amp; typeof(onPlayStart) === "function") { onPlayStart.call(this, startAt, stopAt, startAtNextLoop, loop, avoidDelayedPlay, allowedRecursiveDelay, startPlayingTime); onPlayStart = null; } //Prevents execution again.
1193
1194 return true;
1195}
1196
1197
1198/**
1199 * Resumes the audio (after being paused), starting from the same point it was paused previously.
1200 * @function CB_AudioFile_API.WAAPI#resume
1201 * @param {boolean} [loop={@link CB_AudioFile_API.WAAPI#loop}] - Sets whether we want to play the audio looping (starting again and again) or just play it once. Note that at the end of each loop the "onStop" function defined (through the {@link CB_AudioFile_API.WAAPI#onStop} method) will not be called.
1202 * @param {boolean} [avoidDelayedPlay=false] - If set to false (recommended) and the audio failed previously or was aborted (destroyed), it will try to load it correctly again automatically and play it after that if possible (this can take some time so the audio could start playing after a delay). Otherwise, if set to true and the audio failed or was aborted (destroyed), the audio will not play at all and the {@link CB_AudioFile_API.WAAPI#stop} method will be called immediately.
1203 * @param {boolean} [allowedRecursiveDelay={@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_ALLOWED_RECURSIVE_DELAY_DEFAULT}] - The maximum amount of time (in milliseconds) of delay that we accept before start playing the audio. If the amount of time is overcome, the audio will not play at all and the {@link CB_AudioFile_API.WAAPI#stop} method will be called immediately. Used only when the "avoidDelayedPlay" parameter is set to false and the audio needs to be loaded because it failed previously or was aborted (destroyed).
1204 * @param {function} [onPlayStart] - Function to be called when the audio starts playing successfully. The function will be called with the following parameters (in order): "startAt", "stopAt", "startAtNextLoop", "loop", "avoidDelayedPlay", "allowedRecursiveDelay" and "startPlayingTime", being "this" the {@link CB_AudioFile_API.WAAPI} object. If the audio is looping, this will be called only once when the audio starts playing the first time and it will not be called next loops.
1205 * @param {function} [onLoadError] - Function to be called if the audio cannot be played successfully. It will not be called if the audio is not paused or is stopped. The first and unique parameter will be a string describing the error found (if it could be determined), being "this" the {@link CB_AudioFile_API.WAAPI} object.
1206 * @returns {boolean|integer} Returns the returning value of the {@link CB_AudioFile_API.WAAPI#play} method which is called internally. It returns false if the audio is not paused or it is stopped, returns "-1" if the audio cannot be played and an error is detected or returns true otherwise. Note that even when it returns true there can be a non-detectable error and maybe the audio is not played.
1207 */
1208CB_AudioFile_API["WAAPI"].prototype.resume = function(loop, avoidDelayedPlay, allowedRecursiveDelay, onPlayStart, onLoadError)
1209{
1210 //If it not paused or it is stopped, exits the function:
1211 if (!this.paused || this.stopped) { return false; }
1212
1213 //var startAt = this.pauseTime - (this._startTime * 1000);
1214 var startAt = this.pauseTime;
1215
1216 //If it has been paused after the stop time (happens sometimes when the sound was nearly to finish):
1217 if (startAt >= this.lastStopAt)
1218 {
1219 startAt = this.lastStopAt - 1; //We will begin just 1 millisecond before (otherwise the play method would begin again from lastStartAt).
1220 }
1221
1222 return this.play(startAt, this.lastStopAt, loop, avoidDelayedPlay, allowedRecursiveDelay, onPlayStart, onLoadError, true, false);
1223}
1224
1225
1226/**
1227 * Pauses the audio when it is being played.
1228 * @function CB_AudioFile_API.WAAPI#pause
1229 * @param {function} [onPause] - Function without parameters to be called when the audio is paused successfully, being "this" the {@link CB_AudioFile_API.WAAPI} object.
1230 * @param {boolean} [keepPausedUnaltered=false] - If set to true (not recommended), the {@link CB_AudioFile_API.WAAPI#paused} property will not be set to true and it will remain with its current value.
1231 * @returns {boolean} It returns false if the audio is already paused or it is stopped or if it cannot be paused. Returns true otherwise.
1232 */
1233CB_AudioFile_API["WAAPI"].prototype.pause = function(onPause, keepPausedUnaltered)
1234{
1235 //If it already paused or stopped, exits the function:
1236 if (this.paused || this.stopped) { return false; }
1237
1238 if (typeof(CB_AudioFile_API["WAAPI"].audioContext) !== "undefined" &amp;&amp; CB_AudioFile_API["WAAPI"].audioContext !== null)
1239 {
1240 if (typeof(this.source) !== "undefined" &amp;&amp; this.source !== null)
1241 {
1242 clearTimeout(this._timeoutWhenStop); //Prevents that the file is set as stopped or to be executed again.
1243 if (!keepPausedUnaltered) { this.paused = true; }
1244 //Prevents error on Safari Mobile ("InvalidStateError: DOM Exception 11: An attempt was made to use an object that is not, or is no longer, usable."):
1245 try
1246 {
1247 this.source.stop(0);
1248 } catch(E) {}
1249 //this.pauseTime = CB_AudioFile_API["WAAPI"].audioContext.currentTime * 1000;
1250 this.pauseTime = (CB_AudioFile_API["WAAPI"].audioContext.currentTime - this._startTime) * 1000;
1251 if (typeof(onPause) === "function") { onPause.call(this); }
1252 return true;
1253 }
1254 }
1255 return false;
1256}
1257
1258
1259/**
1260 * Stops the audio.
1261 * @function CB_AudioFile_API.WAAPI#stop
1262 * @param {boolean} [keepStoppedUnaltered=false] - If set to true (not recommended), the {@link CB_AudioFile_API.WAAPI#stopped} property will not be set to true and it will remain with its current value.
1263 * @param {boolean} [avoidOnStop=false] - If set to false and there is an "onStop" function defined (through the {@link CB_AudioFile_API.WAAPI#onStop} method), it will be called after stopping the audio (or after trying to do it, at least) but only if either the "forceOnStop" parameter is set to true or if the "keepStoppedUnaltered" parameter is set to false and the audio was not already stopped before. If set to true, the "onStop" function (if any) will not be called at all.
1264 * @param {boolean} [forceOnStop=false] - If it is set to true and the "avoidOnStop" parameter is set to false and there is an "onStop" function defined (through the {@link CB_AudioFile_API.WAAPI#onStop} method), it will be called regardless the audio was stopped before or not. If set to false, the "onStop" function (if any) will only be called if the "keepStoppedUnaltered" parameter is set to false and the audio was not already stopped before. This parameter will be ignored if the "avoidOnStop" parameter is set to true.
1265 * @returns {boolean} It returns false if the stopping action cannot be performed at all (this could happen when the audio has not been loaded properly, for example). Returns true otherwise (this only means that it has been tried to be stopped but it could not be successfully).
1266 */
1267CB_AudioFile_API["WAAPI"].prototype.stop = function(keepStoppedUnaltered, avoidOnStop, forceOnStop)
1268{
1269 if (typeof(this.source) !== "undefined" &amp;&amp; this.source !== null)
1270 {
1271 ///////clearTimeout(this._timeoutWhenStop); //Prevents that the file is set as stopped or to be executed again.
1272 var stoppedBefore = this.stopped;
1273 if (!keepStoppedUnaltered) { this.stopped = true; }
1274 this.paused = false; //If it is stopped, it is not paused.
1275 //Prevents error on Safari Mobile ("InvalidStateError: DOM Exception 11: An attempt was made to use an object that is not, or is no longer, usable."):
1276 try
1277 {
1278 this.source.stop(0);
1279 } catch(E) {}
1280 //If we do not want to avoid onStop, it was not stopped before but it is now and onStop has a valid function assigned, we execute it:
1281 if (!avoidOnStop &amp;&amp; (!stoppedBefore &amp;&amp; this.stopped || forceOnStop) &amp;&amp; typeof(this.onStopFunction) === "function") { this.onStopFunction.call(this); }
1282 return true;
1283 }
1284 return false;
1285}
1286
1287
1288/**
1289 * Sets the desired volume for the audio file (from 0 to the maximum value, where the maximum value will be the returning value of calling the {@link CB_Speaker.getVolume} function if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is set to true or it will be 100 otherwise).
1290 * @function CB_AudioFile_API.WAAPI#setVolume
1291 * @param {number} [volume={@link CB_Speaker.getVolume()} | {@link CB_Configuration.CrossBase.CB_Speaker_DEFAULT_VOLUME}] - Desired volume (from 0 to the maximum value, where the maximum value will be the returning value of calling the {@link CB_Speaker.getVolume} function if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is set to true or it will be 100 otherwise).
1292 * @param {boolean} [forceSetVolumeProperty=false] - If set to true (not recommended), it will change the {@link CB_AudioFile_API.WAAPI#volume} property even when the volume failed to be changed.
1293 * @param {function} [onSetVolume] - Callback function which will be called if it has been possible to set the volume (or at least it was possible to try it). It will not receive any parameters, being "this" the {@link CB_AudioFile_API.WAAPI} object.
1294 * @param {boolean} [saveForUnmute=false] - If set to true (not recommended), it will save internally the current volume before setting it so it will restore the same volume again after calling the {@link CB_AudioFile_API.WAAPI#unmute} method. Internal usage only recommended.
1295 * @returns {number} Returns the current volume (from 0 to the maximum value, where the maximum value will be the returning value of calling the {@link CB_Speaker.getVolume} function if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is set to true or it will be 100 otherwise).
1296 */
1297CB_AudioFile_API["WAAPI"].prototype.setVolume = function(volume, forceSetVolumeProperty, onSetVolume, saveForUnmute)
1298{
1299 //Defines the default parameters:
1300 volume = parseInt(volume);
1301 if (isNaN(volume))
1302 {
1303 this.DEFAULT_VOLUME = CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_DEFAULT ? CB_Speaker.getVolume() : CB_Configuration[CB_BASE_NAME].CB_Speaker_DEFAULT_VOLUME;
1304 volume = this.DEFAULT_VOLUME;
1305 }
1306
1307 //Sets the volume within their limits if it is beyond them:
1308 var MAX_VOLUME = CB_Configuration[CB_BASE_NAME].CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM ? CB_Speaker.getVolume() : 100;
1309 if (volume > MAX_VOLUME) { volume = MAX_VOLUME; }
1310 else if (volume &lt; 0) { volume = 0; }
1311
1312 if (typeof(this.gainNode) !== "undefined" &amp;&amp; this.gainNode !== null)
1313 {
1314 if (typeof(this.gainNode.gain.setValueAtTime) !== "undefined") { this.gainNode.gain.setValueAtTime(volume / 100, CB_AudioFile_API["WAAPI"].audioContext.currentTime); }
1315 else { this.gainNode.gain.value = volume / 100; } //fraction * fraction;
1316 if ((saveForUnmute || volume === 0) &amp;&amp; this.volume > 0) { this.volumeBeforeMute = this.volume; } //Also saves the previous volume if the desired one is zero (muted).
1317 this.volume = volume;
1318 if (typeof(onSetVolume) === "function") { onSetVolume.call(this); }
1319 }
1320
1321 if (forceSetVolumeProperty) { this.volume = volume; }
1322
1323 return this.volume;
1324}
1325
1326
1327/**
1328 * Mutes the audio file.
1329 * @function CB_AudioFile_API.WAAPI#mute
1330 * @param {function} [onMute] - Callback function which will be called if it has been possible to mute the audio file (or at least it was possible to try it). It will not receive any parameters, being "this" the {@link CB_AudioFile_API.WAAPI} object.
1331 * @returns {number} Returns the current volume (from 0 to the maximum value, where the maximum value will be the returning value of calling the {@link CB_Speaker.getVolume} function if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is set to true or it will be 100 otherwise). If all goes well, the returning value should be zero (0). Note that, even when it returns a zero (0) value, this does not always mean that the mute has been applied successfully.
1332 */
1333CB_AudioFile_API["WAAPI"].prototype.mute = function(onMute)
1334{
1335 //Only mutes the sound if it is not muted already:
1336 if (this.volume > 0)
1337 {
1338 //Mutes the audio:
1339 this.setVolume(0, false, onMute, true); //It modifies this.volumeBeforeMute.
1340 }
1341 return this.volume;
1342}
1343
1344
1345/**
1346 * Restores audio after muting it (unmutes it).
1347 * @function CB_AudioFile_API.WAAPI#unmute
1348 * @param {function} [onUnmute] - Callback function which will be called if it has been possible to unmute the audio file (or at least it was possible to try it). It will not receive any parameters, being "this" the {@link CB_AudioFile_API.WAAPI} object.
1349 * @returns {number} Returns the current volume (from 0 to the maximum value, where the maximum value will be the returning value of calling the {@link CB_Speaker.getVolume} function if the {@link CB_Configuration.CrossBase.CB_AudioFile_AudioFileCache_USE_SPEAKER_VOLUME_AS_MAXIMUM} property is set to true or it will be 100 otherwise).
1350 */
1351CB_AudioFile_API["WAAPI"].prototype.unmute = function(onUnmute)
1352{
1353 //Only unmutes if it is still muted:
1354 if (this.volume === 0)
1355 {
1356 //Restores the volume before muting:
1357 this.setVolume(this.volumeBeforeMute, false, onUnmute);
1358 }
1359 return this.volume;
1360}
1361
1362
1363/**
1364 * Gets the current time (in milliseconds) which belongs to the position where the audio is currently playing or where it has been paused. Note that some audio APIs and clients could give wrong values.
1365 * @function CB_AudioFile_API.WAAPI#getCurrentTime
1366 * @returns {number} Returns the current time (in milliseconds). Note that some audio APIs and clients could give wrong values.
1367 */
1368CB_AudioFile_API["WAAPI"].prototype.getCurrentTime = function()
1369{
1370 var currentTime;
1371
1372 if (typeof(CB_AudioFile_API["WAAPI"].audioContext) !== "undefined" &amp;&amp; CB_AudioFile_API["WAAPI"].audioContext !== null)
1373 {
1374 if (this.stopped)
1375 {
1376 currentTime = 0;
1377 }
1378 else if (this.paused)
1379 {
1380 //currentTime = this.pauseTime - (this._startTime * 1000);
1381 currentTime = this.pauseTime;
1382 }
1383 else
1384 {
1385 currentTime = (CB_AudioFile_API["WAAPI"].audioContext.currentTime - this._startTime) * 1000;
1386 }
1387
1388 //Maybe it is a loop:
1389 var duration = this.getDuration();
1390 if (duration > 0) //Avoids division by zero.
1391 {
1392 currentTime %= duration;
1393 }
1394 }
1395
1396 if (typeof(currentTime) === "undefined" || currentTime === null || isNaN(currentTime) || currentTime &lt; 0) { currentTime = 0; }
1397
1398 return currentTime;
1399}
1400
1401
1402/**
1403 * Sets a function to execute when the audio file stops playing or removes it.
1404 * @function CB_AudioFile_API.WAAPI#onStop
1405 * @param {function|null} callbackFunction - The function (event listener) that we want to execute when the event is fired. No parameters will be received, being "this" the {@link CB_AudioFile_API.WAAPI} object. If a null value is used, the event will be removed.
1406 * @param {boolean} [keepOldFunction=true] - Defines whether we want to keep any possible previous event listener or not.
1407 * @returns {boolean} Returns whether the event has been set or not (removed).
1408 */
1409CB_AudioFile_API["WAAPI"].prototype.onStop = function(callbackFunction, keepOldFunction)
1410{
1411 //If not set, it keeps old function by default:
1412 if (typeof(keepOldFunction) === "undefined" || keepOldFunction === null) { keepOldFunction = true; }
1413
1414 if (typeof(callbackFunction) !== "function") { this.onStopFunction = null; return false; }
1415
1416 //If we don't want to keep the old function:
1417 if (!keepOldFunction)
1418 {
1419 this.onStopFunction = callbackFunction;
1420 }
1421 //...otherwise if we want to keep the old function, we keep it:
1422 else
1423 {
1424 var that = this;
1425 //Stores old function:
1426 var oldFunction = this.onStopFunction; //Stores old function of eventFunctionHolder.
1427 this.onStopFunction =
1428 function()
1429 {
1430 if (typeof(oldFunction) === "function") { oldFunction.call(that); }
1431 callbackFunction.call(that);
1432 };
1433 }
1434
1435 return true;
1436}
1437
1438
1439/**
1440 * Returns a number representing the percentage of the loading progress for the audio file (from 0 to 100, being 100 a complete loading progress). The way to calculate it internally may differ from one audio API to another and it is not totally reliable.
1441 * @function CB_AudioFile_API.WAAPI#getProgress
1442 * @returns {number} Returns a number representing the percentage of the loading progress for the audio file (from 0 to 100, being 100 a complete loading progress). The way to calculate it internally may differ from one audio API to another and it is not totally reliable.
1443 */
1444CB_AudioFile_API["WAAPI"].prototype.getProgress = function()
1445{
1446 if (this.status === CB_AudioFile.LOADED || this.status === CB_AudioFile.UNCHECKED || this.status === CB_AudioFile.CHECKING) { return 100; }
1447 else if (this.status === CB_AudioFile.UNLOADED) { return 0; }
1448
1449 var progress = 0;
1450
1451 //Calculates the progress (only if it is LOADING, FAILED or ABORTED):
1452 //TODO: if decodeAudioData method gets a way to know its progress in the future, use that way here.
1453
1454 //Progress downloading is considered as the 50% of the process (the other 50% would be te decodinng by decodeAudioData method):
1455 progress = this.progressDownloading / 2;
1456
1457 return progress;
1458}</pre>
1459 </article>
1460</section>
1461
1462
1463
1464
1465
1466 </div>
1467 </div>
1468
1469 <div class="clearfix"></div>
1470
1471
1472
1473</div>
1474</div>
1475
1476
1477 <div class="modal fade" id="searchResults">
1478 <div class="modal-dialog">
1479 <div class="modal-content">
1480 <div class="modal-header">
1481 <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
1482 <h4 class="modal-title">Search results</h4>
1483 </div>
1484 <div class="modal-body"></div>
1485 <div class="modal-footer">
1486 <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
1487 </div>
1488 </div><!-- /.modal-content -->
1489 </div><!-- /.modal-dialog -->
1490 </div>
1491
1492
1493<footer>
1494
1495
1496 <span class="copyright">
1497 <a href="printable/" target="_blank">See a more printer-friendly version</a><hr /><span style="color:#000000">© <address style="display:inline; font-style:normal;"><a href="https://crossbrowdy.com/" target="_blank">CrossBrowdy</a> API documentation</address> by <a href="https://joanalbamaldonado.com/" target="_blank">Joan Alba Maldonado</a> - <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">Creative Commons Attribution 4.0 International</a><br />DocStrap Copyright © 2012-2015 The contributors to the JSDoc3 and DocStrap projects.</span>
1498 </span>
1499
1500<span class="jsdoc-message">
1501 Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.2</a>
1502
1503 on Wed Mar 22nd 2023
1504
1505 using the <a href="https://github.com/docstrap/docstrap">DocStrap template</a>.
1506</span>
1507</footer>
1508
1509<script src="scripts/docstrap.lib.js"></script>
1510<script src="scripts/toc.js"></script>
1511
1512 <script type="text/javascript" src="scripts/fulltext-search-ui.js"></script>
1513
1514
1515<script>
1516$( function () {
1517 $( "[id*='$']" ).each( function () {
1518 var $this = $( this );
1519
1520 $this.attr( "id", $this.attr( "id" ).replace( "$", "__" ) );
1521 } );
1522
1523 $( ".tutorial-section pre, .readme-section pre, pre.prettyprint.source" ).each( function () {
1524 var $this = $( this );
1525
1526 var example = $this.find( "code" );
1527 exampleText = example.html();
1528 var lang = /{@lang (.*?)}/.exec( exampleText );
1529 if ( lang && lang[1] ) {
1530 exampleText = exampleText.replace( lang[0], "" );
1531 example.html( exampleText );
1532 lang = lang[1];
1533 } else {
1534 var langClassMatch = example.parent()[0].className.match(/lang\-(\S+)/);
1535 lang = langClassMatch ? langClassMatch[1] : "javascript";
1536 }
1537
1538 if ( lang ) {
1539
1540 $this
1541 .addClass( "sunlight-highlight-" + lang )
1542 .addClass( "linenums" )
1543 .html( example.html() );
1544
1545 }
1546 } );
1547
1548 Sunlight.highlightAll( {
1549 lineNumbers : true,
1550 showMenu : true,
1551 enableDoclinks : true
1552 } );
1553
1554 $.catchAnchorLinks( {
1555 navbarOffset: 10
1556 } );
1557 $( "#toc" ).toc( {
1558 anchorName : function ( i, heading, prefix ) {
1559 return $( heading ).attr( "id" ) || ( prefix + i );
1560 },
1561 selectors : "#toc-content h1,#toc-content h2,#toc-content h3,#toc-content h4",
1562 showAndHide : false,
1563 smoothScrolling: true
1564 } );
1565
1566 $( "#main span[id^='toc']" ).addClass( "toc-shim" );
1567 $( '.dropdown-toggle' ).dropdown();
1568
1569 $( "table" ).each( function () {
1570 var $this = $( this );
1571 $this.addClass('table');
1572 } );
1573
1574} );
1575</script>
1576
1577
1578
1579<!--Navigation and Symbol Display-->
1580
1581<script>
1582 $( function () {
1583 $( '#main' ).localScroll( {
1584 offset : { top : 60 } //offset by the height of your header (give or take a few px, see what works for you)
1585 } );
1586 $( "dt.name" ).each( function () {
1587 var $this = $( this ).find("h4");
1588 var icon = $( "<i/>" ).addClass( "icon-plus-sign" ).addClass( "pull-right" ).addClass( "icon-white" );
1589 var dt = $(this);
1590 var children = dt.next( "dd" );
1591
1592 dt.prepend( icon ).css( {cursor : "pointer"} );
1593 dt.addClass( "member-collapsed" ).addClass( "member" );
1594
1595
1596 children.hide();
1597
1598 dt.children().on( "click", function () {
1599 children = dt.next( "dd" );
1600 children.slideToggle( "fast", function () {
1601
1602 if ( children.is( ":visible" ) ) {
1603 icon.addClass( "icon-minus-sign" ).removeClass( "icon-plus-sign" ).removeClass( "icon-white" );
1604 dt.addClass( "member-open" ).animate( "member-collapsed" );
1605 } else {
1606 icon.addClass( "icon-plus-sign" ).removeClass( "icon-minus-sign" ).addClass( "icon-white" );
1607 dt.addClass( "member-collapsed" ).removeClass( "member-open" );
1608 }
1609 } );
1610 } );
1611
1612 } );
1613 } );
1614</script>
1615
1616
1617<!--Google Analytics-->
1618
1619
1620
1621 <script type="text/javascript">
1622 $(document).ready(function() {
1623 SearcherDisplay.init();
1624 });
1625 </script>
1626
1627
1628</body>
1629</html>