/*! * SoundJS * Visit http://createjs.com/ for documentation, updates and examples. * * Copyright (c) 2010 gskinner.com, inc. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ //############################################################################## // CordovaAudioLoader.js //############################################################################## this.createjs = this.createjs || {}; (function () { "use strict"; /** * Loader provides a mechanism to preload Cordova audio content via PreloadJS or internally. Instances are returned to * the preloader, and the load method is called when the asset needs to be requested. * Currently files are assumed to be local and no loading actually takes place. This class exists to more easily support * the existing architecture. * * @class CordovaAudioLoader * @param {String} loadItem The item to be loaded * @extends XHRRequest * @protected */ function Loader(loadItem) { this.AbstractLoader_constructor(loadItem, true, createjs.Types.SOUND); /** * A Media object used to determine if src exists and to get duration * @property _media * @type {Media} * @protected */ this._media = null; /** * A time counter that triggers timeout if loading takes too long * @property _loadTime * @type {number} * @protected */ this._loadTime = 0; /** * The frequency to fire the loading timer until duration can be retrieved * @property _TIMER_FREQUENCY * @type {number} * @protected */ this._TIMER_FREQUENCY = 100; }; var p = createjs.extend(Loader, createjs.AbstractLoader); // public methods p.load = function() { this._media = new Media(this._item.src, null, createjs.proxy(this._mediaErrorHandler,this)); this._media.seekTo(0); // needed to get duration this._getMediaDuration(); }; p.toString = function () { return "[CordovaAudioLoader]"; }; // private methods /** * Fires if audio cannot seek, indicating that src does not exist. * @method _mediaErrorHandler * @param error * @protected */ p._mediaErrorHandler = function(error) { this._media.release(); this._sendError(); }; /** * will attempt to get duration of audio until successful or time passes this._item.loadTimeout * @method _getMediaDuration * @protected */ p._getMediaDuration = function() { this._result = this._media.getDuration() * 1000; if (this._result < 0) { this._loadTime += this._TIMER_FREQUENCY; if (this._loadTime > this._item.loadTimeout) { this.handleEvent({type:"timeout"}); } else { setTimeout(createjs.proxy(this._getMediaDuration, this), this._TIMER_FREQUENCY); } } else { this._media.release(); this._sendComplete(); } }; createjs.CordovaAudioLoader = createjs.promote(Loader, "AbstractLoader"); }()); //############################################################################## // CordovaAudioSoundInstance.js //############################################################################## this.createjs = this.createjs || {}; (function () { "use strict"; /** * CordovaAudioSoundInstance extends the base api of {{#crossLink "AbstractSoundInstance"}}{{/crossLink}} and is used by * {{#crossLink "CordovaAudioPlugin"}}{{/crossLink}}. * * @param {String} src The path to and file name of the sound. * @param {Number} startTime Audio sprite property used to apply an offset, in milliseconds. * @param {Number} duration Audio sprite property used to set the time the clip plays for, in milliseconds. * @param {Object} playbackResource Any resource needed by plugin to support audio playback. * @class CordovaAudioSoundInstance * @extends AbstractSoundInstance * @constructor */ function CordovaAudioSoundInstance(src, startTime, duration, playbackResource) { this.AbstractSoundInstance_constructor(src, startTime, duration, playbackResource); // Public Properties /** * Sets the playAudioWhenScreenIsLocked property for play calls on iOS devices. * @property playWhenScreenLocked * @type {boolean} */ this.playWhenScreenLocked = null; // Private Properties /** * Used to approximate the playback position by storing the number of milliseconds elapsed since * 1 January 1970 00:00:00 UTC when playing * Note that if js clock is out of sync with Media playback, this will become increasingly inaccurate. * @property _playStartTime * @type {Number} * @protected */ this._playStartTime = null; /** * A TimeOut used to trigger the end and possible loop of audio sprites. * @property _audioSpriteTimeout * @type {null} * @protected */ this._audioSpriteTimeout = null; /** * Boolean value that indicates if we are using an audioSprite * @property _audioSprite * @type {boolean} * @protected */ this._audioSprite = false; // Proxies, make removing listeners easier. this._audioSpriteEndHandler = createjs.proxy(this._handleAudioSpriteComplete, this); this._mediaPlayFinishedHandler = createjs.proxy(this._handleSoundComplete, this); this._mediaErrorHandler = createjs.proxy(this._handleMediaError, this); this._mediaProgressHandler = createjs.proxy(this._handleMediaProgress, this); this._playbackResource = new Media(src, this._mediaPlayFinishedHandler, this._mediaErrorHandler, this._mediaProgressHandler); if (duration) { this._audioSprite = true; } else { this._setDurationFromSource(); } } var p = createjs.extend(CordovaAudioSoundInstance, createjs.AbstractSoundInstance); // Public Methods /** * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master volume. * undoc'd because it is not meant to be used outside of Sound * #method setMasterVolume * @param value */ p.setMasterVolume = function (value) { this._updateVolume(); }; /** * Called by {{#crossLink "Sound"}}{{/crossLink}} when plugin does not handle master mute. * undoc'd because it is not meant to be used outside of Sound * #method setMasterMute * @param value */ p.setMasterMute = function (isMuted) { this._updateVolume(); }; p.destroy = function() { // pause and release the playback resource, then call parent function this._playbackResource.pause(); this._playbackResource.release(); this.AbstractSoundInstance_destroy(); }; /** * Maps to Media.getCurrentPosition, * which is curiously asynchronus and requires a callback. * @method getCurrentPosition * @param {Method} mediaSuccess The callback that is passed the current position in seconds. * @param {Method} [mediaError=null] (Optional) The callback to execute if an error occurs. */ p.getCurrentPosition = function (mediaSuccess, mediaError) { this._playbackResource.getCurrentPosition(mediaSuccess, mediaError); }; p.toString = function () { return "[CordovaAudioSoundInstance]"; }; //Private Methods /** * media object has failed and likely will never work * @method _handleMediaError * @param error * @private */ p._handleMediaError = function(error) { clearTimeout(this.delayTimeoutId); // clear timeout that plays delayed sound this.playState = createjs.Sound.PLAY_FAILED; this._sendEvent("failed"); }; p._handleMediaProgress = function(state) { // do nothing }; p._handleAudioSpriteComplete = function() { this._playbackResource.pause(); this._handleSoundComplete(); }; /* don't need these for current looping approach p._removeLooping = function() { }; p._addLooping = function() { }; */ p._handleCleanUp = function () { clearTimeout(this._audioSpriteTimeout); // OJR cannot use .stop as it prevents .seekTo from working // todo consider media.release }; p._handleSoundReady = function (event) { this._playbackResource.seekTo(this._startTime + this._position); if (this._audioSprite) { this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this._position) } this._playbackResource.play({playAudioWhenScreenIsLocked: this.playWhenScreenLocked}); this._playStartTime = Date.now(); }; p._pause = function () { clearTimeout(this._audioSpriteTimeout); this._playbackResource.pause(); if (this._playStartTime) { this._position = Date.now() - this._playStartTime; this._playStartTime = null; } this._playbackResource.getCurrentPosition(createjs.proxy(this._updatePausePos, this)); }; /** * Synchronizes the best guess position with the actual current position. * @method _updatePausePos * @param {Number} pos The current position in seconds * @private */ p._updatePausePos = function (pos) { this._position = pos * 1000 - this._startTime; if(this._playStartTime) { this._playStartTime = Date.now(); } }; p._resume = function () { if (this._audioSprite) { this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this._position) } this._playbackResource.play({playAudioWhenScreenIsLocked: this.playWhenScreenLocked}); this._playStartTime = Date.now(); }; p._handleStop = function() { clearTimeout(this._audioSpriteTimeout); this._playbackResource.pause(); // cannot use .stop because it prevents .seekTo from working this._playbackResource.seekTo(this._startTime); if (this._playStartTime) { this._position = 0; this._playStartTime = null; } }; p._updateVolume = function () { var newVolume = (this._muted || createjs.Sound._masterMute) ? 0 : this._volume * createjs.Sound._masterVolume; this._playbackResource.setVolume(newVolume); }; p._calculateCurrentPosition = function() { // return best guess position. // Note if Media and js clock are out of sync, this value will become increasingly inaccurate over time if (this._playStartTime) { this._position = Date.now() - this._playStartTime + this._position; this._playStartTime = Date.now(); } return this._position; }; p._updatePosition = function() { this._playbackResource.seekTo(this._startTime + this._position); this._playStartTime = Date.now(); if (this._audioSprite) { clearTimeout(this._audioSpriteTimeout); this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this._position) } }; p._handleLoop = function (event) { this._handleSoundReady(); }; p._updateStartTime = function () { this._audioSprite = true; if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { // do nothing } }; p._updateDuration = function () { this._audioSprite if(this.playState == createjs.Sound.PLAY_SUCCEEDED) { clearTimeout(this._audioSpriteTimeout); this._audioSpriteTimeout = setTimeout(this._audioSpriteEndHandler, this._duration - this.position) } }; p._setDurationFromSource = function () { this._duration = createjs.Sound.activePlugin.getSrcDuration(this.src); // TODO find a better way to do this that does not break flow }; createjs.CordovaAudioSoundInstance = createjs.promote(CordovaAudioSoundInstance, "AbstractSoundInstance"); }()); //############################################################################## // CordovaAudioPlugin.js //############################################################################## this.createjs = this.createjs || {}; (function () { "use strict"; /** * Play sounds using Cordova Media plugin, which will work with a Cordova app and tools that utilize Cordova such as PhoneGap or Ionic. * This plugin is not used by default, and must be registered manually in {{#crossLink "Sound"}}{{/crossLink}} * using the {{#crossLink "Sound/registerPlugins"}}{{/crossLink}} method. * This plugin is recommended when building a Cordova based app, but is not required. * * NOTE the Cordova Media plugin is required * * cordova plugin add org.apache.cordova.media * *