scratch – Blame information for rev 73

Subversion Repositories:
Rev:
Rev Author Line No. Line
73 office 1 /*!
2 * SoundJS
3 * Visit http://createjs.com/ for documentation, updates and examples.
4 *
5 * Copyright (c) 2010 gskinner.com, inc.
6 *
7 * Permission is hereby granted, free of charge, to any person
8 * obtaining a copy of this software and associated documentation
9 * files (the "Software"), to deal in the Software without
10 * restriction, including without limitation the rights to use,
11 * copy, modify, merge, publish, distribute, sublicense, and/or sell
12 * copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following
14 * conditions:
15 *
16 * The above copyright notice and this permission notice shall be
17 * included in all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26 * OTHER DEALINGS IN THE SOFTWARE.
27 */
28  
29  
30 //##############################################################################
31 // CordovaAudioLoader.js
32 //##############################################################################
33  
34 this.createjs = this.createjs || {};
35  
36 (function () {
37 "use strict";
38  
39 /**
40 * Loader provides a mechanism to preload Cordova audio content via PreloadJS or internally. Instances are returned to
41 * the preloader, and the load method is called when the asset needs to be requested.
42 * Currently files are assumed to be local and no loading actually takes place. This class exists to more easily support
43 * the existing architecture.
44 *
45 * @class CordovaAudioLoader
46 * @param {String} loadItem The item to be loaded
47 * @extends XHRRequest
48 * @protected
49 */
50 function Loader(loadItem) {
51 this.AbstractLoader_constructor(loadItem, true, createjs.AbstractLoader.SOUND);
52  
53 /**
54 * A Media object used to determine if src exists and to get duration
55 * @property _media
56 * @type {Media}
57 * @protected
58 */
59 this._media = null;
60  
61 /**
62 * A time counter that triggers timeout if loading takes too long
63 * @property _loadTime
64 * @type {number}
65 * @protected
66 */
67 this._loadTime = 0;
68  
69 /**
70 * The frequency to fire the loading timer until duration can be retrieved
71 * @property _TIMER_FREQUENCY
72 * @type {number}
73 * @protected
74 */
75 this._TIMER_FREQUENCY = 100;
76 };
77 var p = createjs.extend(Loader, createjs.AbstractLoader);
78  
79  
80 // public methods
81 p.load = function() {
82 this._media = new Media(this._item.src, null, createjs.proxy(this._mediaErrorHandler,this));
83 this._media.seekTo(0); // needed to get duration
84  
85 this._getMediaDuration();
86 };
87  
88 p.toString = function () {
89 return "[CordovaAudioLoader]";
90 };
91  
92  
93 // private methods
94 /**
95 * Fires if audio cannot seek, indicating that src does not exist.
96 * @method _mediaErrorHandler
97 * @param error
98 * @protected
99 */
100 p._mediaErrorHandler = function(error) {
101 this._media.release();
102 this._sendError();
103 };
104  
105 /**
106 * will attempt to get duration of audio until successful or time passes this._item.loadTimeout
107 * @method _getMediaDuration
108 * @protected
109 */
110 p._getMediaDuration = function() {
111 this._result = this._media.getDuration() * 1000;
112 if (this._result < 0) {
113 this._loadTime += this._TIMER_FREQUENCY;
114 if (this._loadTime > this._item.loadTimeout) {
115 this.handleEvent({type:"timeout"});
116 } else {
117 setTimeout(createjs.proxy(this._getMediaDuration, this), this._TIMER_FREQUENCY);
118 }
119 } else {
120 this._media.release();
121 this._sendComplete();
122 }
123 };
124  
125 createjs.CordovaAudioLoader = createjs.promote(Loader, "AbstractLoader");
126 }());
127  
128 //##############################################################################
129 // CordovaAudioSoundInstance.js
130 //##############################################################################
131  
132 this.createjs = this.createjs || {};
133  
134 (function () {
135 "use strict";
136  
137 /**
138 * CordovaAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by
139 * {{#crossLink "CordovaAudioPlugin"}}{{/crossLink}}.
140 *
141 * @param {String} src The path to and file name of the sound.
142 * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds.
143 * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds.
144 * @param {Object} playbackResource Any resource needed by plugin to support audio playback.
145 * @class CordovaAudioSoundInstance
146 * @extends AbstractSoundInstance
147 * @constructor
148 */
149 function CordovaAudioSoundInstance(src, startTime, duration, playbackResource) {
150 this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource);
151  
152 // Public Properties
153 /**
154 * Sets the playAudioWhenScreenIsLocked property for play calls on iOS devices.
155 * @property playWhenScreenLocked
156 * @type {boolean}
157 */
158 this.playWhenScreenLocked = null;
159  
160 // Private Properties
161 /**
162 * Used to approximate the playback position by storing the number of milliseconds elapsed since
163 * 1 January 1970 00:00:00 UTC when playing
164 * Note that if js clock is out of sync with Media playback, this will become increasingly inaccurate.
165 * @property _playStartTime
166 * @type {Number}
167 * @protected
168 */
169 this._playStartTime = null;
170  
171 /**
172 * A TimeOut used to trigger the end and possible loop of audio sprites.
173 * @property _audioSpriteTimeout
174 * @type {null}
175 * @protected
176 */
177 this._audioSpriteTimeout = null;
178  
179 /**
180 * Boolean value that indicates if we are using an audioSprite
181 * @property _audioSprite
182 * @type {boolean}
183 * @protected
184 */
185 this._audioSprite = false;
186  
187 // Proxies, make removing listeners easier.
188 this._audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteComplete, this);
189 this._mediaPlayFinishedHandler = createjs.proxy(this._handleSoundComplete, this);
190 this._mediaErrorHandler = createjs.proxy(this._handleMediaError, this);
191 this._mediaProgressHandler = createjs.proxy(this._handleMediaProgress, this);
192  
193 this._playbackResource = new Media(src, this._mediaPlayFinishedHandler, this._mediaErrorHandler, this._mediaProgressHandler);
194  
195 if (duration) {
196 this._audioSprite = true;
197 } else {
198 this._setDurationFromSource();
199 }
200 }
201 var p = createjs.extend(CordovaAudioSoundInstance, createjs.AbstractSoundInstance);
202  
203  
204 // Public Methods
205 /**
206 * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master volume.
207 * undoc'd because it is not meant to be used outside of Sound
208 * #method setMasterVolume
209 * @param value
210 */
211 p.setMasterVolume = function (value) {
212 this._updateVolume();
213 };
214  
215 /**
216 * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master mute.
217 * undoc'd because it is not meant to be used outside of Sound
218 * #method setMasterMute
219 * @param value
220 */
221 p.setMasterMute = function (isMuted) {
222 this._updateVolume();
223 };
224  
225 p.destroy = function() {
226 // call parent function, then release
227 this.AbstractSoundInstance_destroy();
228 this._playbackResource.release();
229 };
230  
231 /**
232 * Maps to <a href="http://plugins.cordova.io/#/package/org.apache.cordova.media" target="_blank">Media.getCurrentPosition</a>,
233 * which is curiously asynchronus and requires a callback.
234 * @method getCurrentPosition
235 * @param {Method} mediaSuccess The callback that is passed the current position in seconds.
236 * @param {Method} [mediaError=null] (Optional) The callback to execute if an error occurs.
237 */
238 p.getCurrentPosition = function (mediaSuccess, mediaError) {
239 this._playbackResource.getCurrentPosition(mediaSuccess, mediaError);
240 };
241  
242 p.toString = function () {
243 return "[CordovaAudioSoundInstance]";
244 };
245  
246 //Private Methods
247 /**
248 * media object has failed and likely will never work
249 * @method _handleMediaError
250 * @param error
251 * @private
252 */
253 p._handleMediaError = function(error) {
254 clearTimeout(this.delayTimeoutId); // clear timeout that plays delayed sound
255  
256 this.playState = createjs.Sound.PLAY_FAILED;
257 this._sendEvent("failed");
258 };
259  
260 p._handleMediaProgress = function(state) {
261 // do nothing
262 };
263  
264 p._handleAudioSpriteComplete = function() {
265 this._playbackResource.pause();
266 this._handleSoundComplete();
267 };
268 /* don't need these for current looping approach
269 p._removeLooping = function() {
270 };
271  
272 p._addLooping = function() {
273 };
274 */
275  
276 p._handleCleanUp = function () {
277 clearTimeout(this._audioSpriteTimeout);
278 this._playbackResource.pause(); // OJR cannot use .stop as it prevents .seekTo from working
279 // todo consider media.release
280 };
281  
282 p._handleSoundReady = function (event) {
283 this._playbackResource.seekTo(this._startTime + this._position);
284  
285 if (this._audioSprite) {
286 this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this._position)
287 }
288  
289 this._playbackResource.play({playAudioWhenScreenIsLocked: this.playWhenScreenLocked});
290 this._playStartTime = Date.now();
291 };
292  
293 p._pause = function () {
294 clearTimeout(this._audioSpriteTimeout);
295 this._playbackResource.pause();
296 if (this._playStartTime) {
297 this._position = Date.now() - this._playStartTime;
298 this._playStartTime = null;
299 }
300 this._playbackResource.getCurrentPosition(createjs.proxy(this._updatePausePos, this));
301 };
302  
303 /**
304 * Synchronizes the best guess position with the actual current position.
305 * @method _updatePausePos
306 * @param {Number} pos The current position in seconds
307 * @private
308 */
309 p._updatePausePos = function (pos) {
310 this._position = pos * 1000 - this._startTime;
311 if(this._playStartTime) {
312 this._playStartTime = Date.now();
313 }
314 };
315  
316 p._resume = function () {
317 if (this._audioSprite) {
318 this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this._position)
319 }
320  
321 this._playbackResource.play({playAudioWhenScreenIsLocked: this.playWhenScreenLocked});
322 this._playStartTime = Date.now();
323 };
324  
325 p._handleStop = function() {
326 clearTimeout(this._audioSpriteTimeout);
327 this._playbackResource.pause(); // cannot use .stop because it prevents .seekTo from working
328 this._playbackResource.seekTo(this._startTime);
329 if (this._playStartTime) {
330 this._position = 0;
331 this._playStartTime = null;
332 }
333 };
334  
335 p._updateVolume = function () {
336 var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume;
337 this._playbackResource.setVolume(newVolume);
338 };
339  
340 p._calculateCurrentPosition = function() {
341 // return best guess position.
342 // Note if Media and js clock are out of sync, this value will become increasingly inaccurate over time
343 if (this._playStartTime) {
344 this._position = Date.now() - this._playStartTime + this._position;
345 this._playStartTime = Date.now();
346 }
347 return this._position;
348 };
349  
350 p._updatePosition = function() {
351 this._playbackResource.seekTo(this._startTime + this._position);
352 this._playStartTime = Date.now();
353 if (this._audioSprite) {
354 clearTimeout(this._audioSpriteTimeout);
355 this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this._position)
356 }
357 };
358  
359 p._handleLoop = function (event) {
360 this._handleSoundReady();
361 };
362  
363 p._updateStartTime = function () {
364 this._audioSprite = true;
365  
366 if(this.playState == createjs.Sound.PLAY_SUCCEEDED) {
367 // do nothing
368 }
369 };
370  
371 p._updateDuration = function () {
372 this._audioSprite
373  
374 if(this.playState == createjs.Sound.PLAY_SUCCEEDED) {
375 clearTimeout(this._audioSpriteTimeout);
376 this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this.position)
377 }
378 };
379  
380 p._setDurationFromSource = function () {
381 this._duration = createjs.Sound.activePlugin.getSrcDuration(this.src); // TODO find a better way to do this that does not break flow
382 };
383  
384 createjs.CordovaAudioSoundInstance = createjs.promote(CordovaAudioSoundInstance, "AbstractSoundInstance");
385 }());
386  
387 //##############################################################################
388 // CordovaAudioPlugin.js
389 //##############################################################################
390  
391 this.createjs = this.createjs || {};
392  
393 (function () {
394  
395 "use strict";
396  
397 /**
398 * Play sounds using Cordova Media plugin, which will work with a Cordova app and tools that utilize Cordova such as PhoneGap or Ionic.
399 * This plugin is not used by default, and must be registered manually in {{#crossLink "Sound"}}{{/crossLink}}
400 * using the {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method.
401 * This plugin is recommended when building a Cordova based app, but is not required.
402 *
403 * <b>NOTE the <a href="http://plugins.cordova.io/#/package/org.apache.cordova.media" target="_blank">Cordova Media plugin</a> is required</b>
404 *
405 * cordova plugin add org.apache.cordova.media
406 *
407 * <h4>Known Issues</h4>
408 * <b>Audio Position</b>
409 * <ul>Audio position is calculated asynchronusly by Media. The SoundJS solution to this problem is two-fold:
410 * <li>Provide {{#crossLink "CordovaAudioSoundInstance/getCurrentPosition"}}{{/crossLink}} that maps directly to media.getCurrentPosition.</li>
411 * <li>Provide a best guess position based on elapsed time since playback started, which is synchronized with actual position when the audio is paused or stopped.
412 * Testing showed this to be fairly reliable within 200ms.</li></ul>
413 * <b>Cordova Media Docs</b>
414 * <ul><li>See the <a href="http://plugins.cordova.io/#/package/org.apache.cordova.media" target="_blank">Cordova Media Docs</a> for various known OS issues.</li></ul>
415 * <br />
416 *
417 * @class CordovaAudioPlugin
418 * @extends AbstractPlugin
419 * @constructor
420 */
421 function CordovaAudioPlugin() {
422 this.AbstractPlugin_constructor();
423  
424 this._capabilities = s._capabilities;
425  
426 this._loaderClass = createjs.CordovaAudioLoader;
427 this._soundInstanceClass = createjs.CordovaAudioSoundInstance;
428  
429 this._srcDurationHash = {};
430 }
431  
432 var p = createjs.extend(CordovaAudioPlugin, createjs.AbstractPlugin);
433 var s = CordovaAudioPlugin;
434  
435  
436 // Static Properties
437 /**
438 * Sets a default playAudioWhenScreenIsLocked property for play calls on iOS devices.
439 * Individual SoundInstances can alter the default with {{#crossLink "CordovaAudioSoundInstance/playWhenScreenLocked"}}{{/crossLink}}.
440 * @property playWhenScreenLocked
441 * @type {boolean}
442 * @static
443 */
444 s.playWhenScreenLocked = false;
445  
446 /**
447 * The capabilities of the plugin. This is generated via the {{#crossLink "CordovaAudioPlugin/_generateCapabilities"}}{{/crossLink}}
448 * method. Please see the Sound {{#crossLink "Sound/getCapabilities"}}{{/crossLink}} method for an overview of all
449 * of the available properties.
450 * @property _capabilities
451 * @type {Object}
452 * @protected
453 * @static
454 */
455 s._capabilities = null;
456  
457  
458 // Static Methods
459 /**
460 * Determine if the plugin can be used in the current browser/OS. Note that HTML audio is available in most modern
461 * browsers, but is disabled in iOS because of its limitations.
462 * @method isSupported
463 * @return {Boolean} If the plugin can be initialized.
464 * @static
465 */
466 s.isSupported = function () {
467 s._generateCapabilities();
468 return (s._capabilities != null);
469 };
470  
471 /**
472 * Determine the capabilities of the plugin. Used internally. Please see the Sound API {{#crossLink "Sound/getCapabilities"}}{{/crossLink}}
473 * method for an overview of plugin capabilities.
474 * @method _generateCapabilities
475 * @static
476 * @protected
477 */
478 s._generateCapabilities = function () {
479 if (s._capabilities != null || !(window.cordova || window.PhoneGap || window.phonegap) || !window.Media) {return;}
480  
481 // OJR my best guess is that Cordova will have the same limits on playback that the audio tag has, but this could be wrong
482 var t = document.createElement("audio");
483 if (t.canPlayType == null) {return null;}
484  
485 s._capabilities = {
486 panning:false,
487 volume:true,
488 tracks:-1
489 };
490  
491 // determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS
492 var supportedExtensions = createjs.Sound.SUPPORTED_EXTENSIONS;
493 var extensionMap = createjs.Sound.EXTENSION_MAP;
494 for (var i = 0, l = supportedExtensions.length; i < l; i++) {
495 var ext = supportedExtensions[i];
496 var playType = extensionMap[ext] || ext;
497 s._capabilities[ext] = (t.canPlayType("audio/" + ext) != "no" && t.canPlayType("audio/" + ext) != "") || (t.canPlayType("audio/" + playType) != "no" && t.canPlayType("audio/" + playType) != "");
498 } // OJR another way to do this might be canPlayType:"m4a", codex: mp4
499 };
500  
501  
502 // public methods
503 p.create = function (src, startTime, duration) {
504 var si = this.AbstractPlugin_create(src, startTime, duration);
505 si.playWhenScreenLocked = this.playWhenScreenLocked;
506 return si;
507 };
508  
509 p.toString = function () {
510 return "[CordovaAudioPlugin]";
511 };
512  
513 // plugin does not support these
514 p.setVolume = p.getVolume = p.setMute = null;
515  
516 /**
517 * Get the duration for a src. Intended for internal use by CordovaAudioSoundInstance.
518 * @method getSrcDuration
519 * @param src
520 * @returns {Number} The duration of the src or null if it does not exist
521 */
522 p.getSrcDuration = function(src) {
523 return this._srcDurationHash[src];
524 };
525  
526 // Private Methods
527 p._handlePreloadComplete = function (event) {
528 var src = event.target.getItem().src;
529 this._srcDurationHash[src] = event.result;
530 this._audioSources[src] = event.result;
531 //this.AbstractPlugin__handlePreloadComplete(event); // we don't want to do the rest of this
532 };
533  
534 p.removeSound = function (src) {
535 delete(this._srcDurationHash[src]);
536 this.AbstractPlugin_removeSound(src);
537 };
538  
539 createjs.CordovaAudioPlugin = createjs.promote(CordovaAudioPlugin, "AbstractPlugin");
540 }());
541  
542 //##############################################################################
543 // version_cordovaplugin.js
544 //##############################################################################
545  
546 this.createjs = this.createjs || {};
547  
548 (function () {
549  
550 var s = createjs.CordovaAudioPlugin = createjs.CordovaAudioPlugin || {};
551  
552 /**
553 * The version string for this release.
554 * @for CordovaAudioPlugin
555 * @property version
556 * @type String
557 * @static
558 **/
559 s.version = /*=version*/"0.6.2"; // injected by build process
560  
561 /**
562 * The build date for this release in UTC format.
563 * @for CordovaAudioPlugin
564 * @property buildDate
565 * @type String
566 * @static
567 **/
568 s.buildDate = /*=date*/"Thu, 26 Nov 2015 20:44:31 GMT"; // injected by build process
569  
570 })();