/* * CordovaAudioSoundInstance * Visit http://createjs.com/ for documentation, updates and examples. * * * Copyright (c) 2012 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. */ /** * @module SoundJS */ // namespace: 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 <a href="http://plugins.cordova.io/#/package/org.apache.cordova.media" target="_blank">Media.getCurrentPosition</a>, * 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"); }());