2014-01-03 13:32:13 -05:00
/ * *
* @ module SoundJS
* /
this . createjs = this . createjs || { } ;
( function ( ) {
/ * *
* Static class holding library specific information such as the version and buildDate of the library .
* The SoundJS class has been renamed { { # crossLink "Sound" } } { { / c r o s s L i n k } } . P l e a s e s e e { { # c r o s s L i n k " S o u n d " } } { { / c r o s s L i n k } }
* for information on using sound .
* @ class SoundJS
* * /
var s = createjs . SoundJS = createjs . SoundJS || { } ;
/ * *
* The version string for this release .
* @ property version
* @ type String
* @ static
* * /
s . version = /*version*/ "NEXT" ; // injected by build process
/ * *
* The build date for this release in UTC format .
* @ property buildDate
* @ type String
* @ static
* * /
2014-11-18 18:26:26 -05:00
s . buildDate = /*date*/ "Mon, 27 Oct 2014 20:40:07 GMT" ; // injected by build process
2014-01-03 13:32:13 -05:00
} ) ( ) ;
/ *
* EventDispatcher
* 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 .
* /
/ * *
* @ module CreateJS
* /
// namespace:
this . createjs = this . createjs || { } ;
( function ( ) {
"use strict" ;
/ * *
* EventDispatcher provides methods for managing queues of event listeners and dispatching events .
*
* You can either extend EventDispatcher or mix its methods into an existing prototype or instance by using the
* EventDispatcher { { # crossLink "EventDispatcher/initialize" } } { { / c r o s s L i n k } } m e t h o d .
*
* Together with the CreateJS Event class , EventDispatcher provides an extended event model that is based on the
* DOM Level 2 event model , including addEventListener , removeEventListener , and dispatchEvent . It supports
* bubbling / capture , preventDefault , stopPropagation , stopImmediatePropagation , and handleEvent .
*
* EventDispatcher also exposes a { { # crossLink "EventDispatcher/on" } } { { / c r o s s L i n k } } m e t h o d , w h i c h m a k e s i t e a s i e r
* to create scoped listeners , listeners that only run once , and listeners with associated arbitrary data . The
* { { # crossLink "EventDispatcher/off" } } { { / c r o s s L i n k } } m e t h o d i s m e r e l y a n a l i a s t o
* { { # crossLink "EventDispatcher/removeEventListener" } } { { / c r o s s L i n k } } .
*
* Another addition to the DOM Level 2 model is the { { # crossLink "EventDispatcher/removeAllEventListeners" } } { { / c r o s s L i n k } }
* method , which can be used to listeners for all events , or listeners for a specific event . The Event object also
* includes a { { # crossLink "Event/remove" } } { { / c r o s s L i n k } } m e t h o d w h i c h r e m o v e s t h e a c t i v e l i s t e n e r .
*
* < h4 > Example < / h 4 >
* Add EventDispatcher capabilities to the "MyClass" class .
*
* EventDispatcher . initialize ( MyClass . prototype ) ;
*
* Add an event ( see { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } ) .
*
* instance . addEventListener ( "eventName" , handlerMethod ) ;
* function handlerMethod ( event ) {
* console . log ( event . target + " Was Clicked" ) ;
* }
*
* < b > Maintaining proper scope < /b><br / >
* Scope ( ie . "this" ) can be be a challenge with events . Using the { { # crossLink "EventDispatcher/on" } } { { / c r o s s L i n k } }
* method to subscribe to events simplifies this .
*
* instance . addEventListener ( "click" , function ( event ) {
* console . log ( instance == this ) ; // false, scope is ambiguous.
* } ) ;
*
* instance . on ( "click" , function ( event ) {
* console . log ( instance == this ) ; // true, "on" uses dispatcher scope by default.
* } ) ;
*
* If you want to use addEventListener instead , you may want to use function . bind ( ) or a similar proxy to manage scope .
*
*
* @ class EventDispatcher
* @ constructor
* * /
var EventDispatcher = function ( ) {
/* this.initialize(); */ // not needed.
} ;
var p = EventDispatcher . prototype ;
2014-11-18 18:26:26 -05:00
EventDispatcher . prototype . constructor = EventDispatcher ;
2014-01-03 13:32:13 -05:00
/ * *
* Static initializer to mix EventDispatcher methods into a target object or prototype .
*
* EventDispatcher . initialize ( MyClass . prototype ) ; // add to the prototype of the class
* EventDispatcher . initialize ( myObject ) ; // add to a specific instance
*
* @ method initialize
* @ static
* @ param { Object } target The target object to inject EventDispatcher methods into . This can be an instance or a
* prototype .
* * /
EventDispatcher . initialize = function ( target ) {
target . addEventListener = p . addEventListener ;
target . on = p . on ;
target . removeEventListener = target . off = p . removeEventListener ;
target . removeAllEventListeners = p . removeAllEventListeners ;
target . hasEventListener = p . hasEventListener ;
target . dispatchEvent = p . dispatchEvent ;
target . _dispatchEvent = p . _dispatchEvent ;
2014-02-02 19:31:06 -05:00
target . willTrigger = p . willTrigger ;
2014-01-03 13:32:13 -05:00
} ;
// constructor:
// private properties:
/ * *
* @ protected
* @ property _listeners
* @ type Object
* * /
p . _listeners = null ;
/ * *
* @ protected
* @ property _captureListeners
* @ type Object
* * /
p . _captureListeners = null ;
// constructor:
/ * *
* Initialization method .
* @ method initialize
* @ protected
* * /
p . initialize = function ( ) { } ;
// public methods:
/ * *
* Adds the specified event listener . Note that adding multiple listeners to the same function will result in
* multiple callbacks getting fired .
*
* < h4 > Example < / h 4 >
*
* displayObject . addEventListener ( "click" , handleClick ) ;
* function handleClick ( event ) {
* // Click happened.
* }
*
* @ method addEventListener
* @ param { String } type The string type of the event .
* @ param { Function | Object } listener An object with a handleEvent method , or a function that will be called when
* the event is dispatched .
* @ param { Boolean } [ useCapture ] For events that bubble , indicates whether to listen for the event in the capture or bubbling / target phase .
* @ return { Function | Object } Returns the listener for chaining or assignment .
* * /
p . addEventListener = function ( type , listener , useCapture ) {
var listeners ;
if ( useCapture ) {
listeners = this . _captureListeners = this . _captureListeners || { } ;
} else {
listeners = this . _listeners = this . _listeners || { } ;
}
var arr = listeners [ type ] ;
if ( arr ) { this . removeEventListener ( type , listener , useCapture ) ; }
arr = listeners [ type ] ; // remove may have deleted the array
if ( ! arr ) { listeners [ type ] = [ listener ] ; }
else { arr . push ( listener ) ; }
return listener ;
} ;
/ * *
* A shortcut method for using addEventListener that makes it easier to specify an execution scope , have a listener
* only run once , associate arbitrary data with the listener , and remove the listener .
*
* This method works by creating an anonymous wrapper function and subscribing it with addEventListener .
* The created anonymous function is returned for use with . removeEventListener ( or . off ) .
*
* < h4 > Example < / h 4 >
*
* var listener = myBtn . on ( "click" , handleClick , null , false , { count : 3 } ) ;
* function handleClick ( evt , data ) {
* data . count -= 1 ;
* console . log ( this == myBtn ) ; // true - scope defaults to the dispatcher
* if ( data . count == 0 ) {
* alert ( "clicked 3 times!" ) ;
* myBtn . off ( "click" , listener ) ;
* // alternately: evt.remove();
* }
* }
*
* @ method on
* @ param { String } type The string type of the event .
* @ param { Function | Object } listener An object with a handleEvent method , or a function that will be called when
* the event is dispatched .
* @ param { Object } [ scope ] The scope to execute the listener in . Defaults to the dispatcher / currentTarget for function listeners , and to the listener itself for object listeners ( ie . using handleEvent ) .
* @ param { Boolean } [ once = false ] If true , the listener will remove itself after the first time it is triggered .
* @ param { * } [ data ] Arbitrary data that will be included as the second parameter when the listener is called .
* @ param { Boolean } [ useCapture = false ] For events that bubble , indicates whether to listen for the event in the capture or bubbling / target phase .
* @ return { Function } Returns the anonymous function that was created and assigned as the listener . This is needed to remove the listener later using . removeEventListener .
* * /
p . on = function ( type , listener , scope , once , data , useCapture ) {
if ( listener . handleEvent ) {
scope = scope || listener ;
listener = listener . handleEvent ;
}
scope = scope || this ;
return this . addEventListener ( type , function ( evt ) {
listener . call ( scope , evt , data ) ;
once && evt . remove ( ) ;
} , useCapture ) ;
} ;
/ * *
* Removes the specified event listener .
*
* < b > Important Note : < / b > t h a t y o u m u s t p a s s t h e e x a c t f u n c t i o n r e f e r e n c e u s e d w h e n t h e e v e n t w a s a d d e d . I f a p r o x y
* function , or function closure is used as the callback , the proxy / closure reference must be used - a new proxy or
* closure will not work .
*
* < h4 > Example < / h 4 >
*
* displayObject . removeEventListener ( "click" , handleClick ) ;
*
* @ method removeEventListener
* @ param { String } type The string type of the event .
* @ param { Function | Object } listener The listener function or object .
* @ param { Boolean } [ useCapture ] For events that bubble , indicates whether to listen for the event in the capture or bubbling / target phase .
* * /
p . removeEventListener = function ( type , listener , useCapture ) {
var listeners = useCapture ? this . _captureListeners : this . _listeners ;
if ( ! listeners ) { return ; }
var arr = listeners [ type ] ;
if ( ! arr ) { return ; }
for ( var i = 0 , l = arr . length ; i < l ; i ++ ) {
if ( arr [ i ] == listener ) {
if ( l == 1 ) { delete ( listeners [ type ] ) ; } // allows for faster checks.
else { arr . splice ( i , 1 ) ; }
break ;
}
}
} ;
/ * *
* A shortcut to the removeEventListener method , with the same parameters and return value . This is a companion to the
* . on method .
*
* @ method off
* @ param { String } type The string type of the event .
* @ param { Function | Object } listener The listener function or object .
* @ param { Boolean } [ useCapture ] For events that bubble , indicates whether to listen for the event in the capture or bubbling / target phase .
* * /
p . off = p . removeEventListener ;
/ * *
* Removes all listeners for the specified type , or all listeners of all types .
*
* < h4 > Example < / h 4 >
*
* // Remove all listeners
* displayObject . removeAllEventListeners ( ) ;
*
* // Remove all click listeners
* displayObject . removeAllEventListeners ( "click" ) ;
*
* @ method removeAllEventListeners
* @ param { String } [ type ] The string type of the event . If omitted , all listeners for all types will be removed .
* * /
p . removeAllEventListeners = function ( type ) {
if ( ! type ) { this . _listeners = this . _captureListeners = null ; }
else {
if ( this . _listeners ) { delete ( this . _listeners [ type ] ) ; }
if ( this . _captureListeners ) { delete ( this . _captureListeners [ type ] ) ; }
}
} ;
/ * *
* Dispatches the specified event to all listeners .
*
* < h4 > Example < / h 4 >
*
* // Use a string event
* this . dispatchEvent ( "complete" ) ;
*
* // Use an Event instance
* var event = new createjs . Event ( "progress" ) ;
* this . dispatchEvent ( event ) ;
*
* @ method dispatchEvent
* @ param { Object | String | Event } eventObj An object with a "type" property , or a string type .
* While a generic object will work , it is recommended to use a CreateJS Event instance . If a string is used ,
* dispatchEvent will construct an Event instance with the specified type .
* @ return { Boolean } Returns the value of eventObj . defaultPrevented .
* * /
2014-11-18 18:26:26 -05:00
p . dispatchEvent = function ( eventObj ) {
2014-01-03 13:32:13 -05:00
if ( typeof eventObj == "string" ) {
// won't bubble, so skip everything if there's no listeners:
var listeners = this . _listeners ;
if ( ! listeners || ! listeners [ eventObj ] ) { return false ; }
eventObj = new createjs . Event ( eventObj ) ;
2014-11-18 18:26:26 -05:00
} else if ( eventObj . target && eventObj . clone ) {
// redispatching an active event object, so clone it:
eventObj = eventObj . clone ( ) ;
2014-01-03 13:32:13 -05:00
}
2014-11-18 18:26:26 -05:00
try { eventObj . target = this ; } catch ( e ) { } // try/catch allows redispatching of native events
2014-01-03 13:32:13 -05:00
if ( ! eventObj . bubbles || ! this . parent ) {
this . _dispatchEvent ( eventObj , 2 ) ;
} else {
var top = this , list = [ top ] ;
while ( top . parent ) { list . push ( top = top . parent ) ; }
var i , l = list . length ;
// capture & atTarget
for ( i = l - 1 ; i >= 0 && ! eventObj . propagationStopped ; i -- ) {
list [ i ] . _dispatchEvent ( eventObj , 1 + ( i == 0 ) ) ;
}
// bubbling
for ( i = 1 ; i < l && ! eventObj . propagationStopped ; i ++ ) {
list [ i ] . _dispatchEvent ( eventObj , 3 ) ;
}
}
return eventObj . defaultPrevented ;
} ;
/ * *
2014-02-02 19:31:06 -05:00
* Indicates whether there is at least one listener for the specified event type .
2014-01-03 13:32:13 -05:00
* @ method hasEventListener
* @ param { String } type The string type of the event .
* @ return { Boolean } Returns true if there is at least one listener for the specified event .
* * /
p . hasEventListener = function ( type ) {
var listeners = this . _listeners , captureListeners = this . _captureListeners ;
return ! ! ( ( listeners && listeners [ type ] ) || ( captureListeners && captureListeners [ type ] ) ) ;
} ;
2014-02-02 19:31:06 -05:00
/ * *
* Indicates whether there is at least one listener for the specified event type on this object or any of its
* ancestors ( parent , parent ' s parent , etc ) . A return value of true indicates that if a bubbling event of the
* specified type is dispatched from this object , it will trigger at least one listener .
*
* This is similar to { { # crossLink "EventDispatcher/hasEventListener" } } { { / c r o s s L i n k } } , b u t i t s e a r c h e s t h e e n t i r e
* event flow for a listener , not just this object .
* @ method willTrigger
* @ param { String } type The string type of the event .
* @ return { Boolean } Returns ` true ` if there is at least one listener for the specified event .
* * /
p . willTrigger = function ( type ) {
var o = this ;
while ( o ) {
if ( o . hasEventListener ( type ) ) { return true ; }
o = o . parent ;
}
return false ;
} ;
2014-01-03 13:32:13 -05:00
/ * *
* @ method toString
* @ return { String } a string representation of the instance .
* * /
p . toString = function ( ) {
return "[EventDispatcher]" ;
} ;
// private methods:
/ * *
* @ method _dispatchEvent
* @ param { Object | String | Event } eventObj
* @ param { Object } eventPhase
* @ protected
* * /
p . _dispatchEvent = function ( eventObj , eventPhase ) {
var l , listeners = ( eventPhase == 1 ) ? this . _captureListeners : this . _listeners ;
if ( eventObj && listeners ) {
var arr = listeners [ eventObj . type ] ;
if ( ! arr || ! ( l = arr . length ) ) { return ; }
2014-11-18 18:26:26 -05:00
try { eventObj . currentTarget = this ; } catch ( e ) { }
try { eventObj . eventPhase = eventPhase ; } catch ( e ) { }
2014-01-03 13:32:13 -05:00
eventObj . removed = false ;
arr = arr . slice ( ) ; // to avoid issues with items being removed or added during the dispatch
for ( var i = 0 ; i < l && ! eventObj . immediatePropagationStopped ; i ++ ) {
var o = arr [ i ] ;
if ( o . handleEvent ) { o . handleEvent ( eventObj ) ; }
else { o ( eventObj ) ; }
if ( eventObj . removed ) {
this . off ( eventObj . type , o , eventPhase == 1 ) ;
eventObj . removed = false ;
}
}
}
} ;
createjs . EventDispatcher = EventDispatcher ;
} ( ) ) ;
/ *
* Event
* 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 .
* /
/ * *
* A collection of Classes that are shared across all the CreateJS libraries . The classes are included in the minified
* files of each library and are available on the createsjs namespace directly .
*
* < h4 > Example < / h 4 >
2014-11-18 18:26:26 -05:00
*
2014-01-03 13:32:13 -05:00
* myObject . addEventListener ( "change" , createjs . proxy ( myMethod , scope ) ) ;
*
* @ module CreateJS
* @ main CreateJS
* /
// namespace:
this . createjs = this . createjs || { } ;
( function ( ) {
"use strict" ;
/ * *
* Contains properties and methods shared by all events for use with
* { { # crossLink "EventDispatcher" } } { { / c r o s s L i n k } } .
*
* Note that Event objects are often reused , so you should never
* rely on an event object ' s state outside of the call stack it was received in .
* @ class Event
* @ param { String } type The event type .
* @ param { Boolean } bubbles Indicates whether the event will bubble through the display list .
* @ param { Boolean } cancelable Indicates whether the default behaviour of this event can be cancelled .
* @ constructor
* * /
var Event = function ( type , bubbles , cancelable ) {
this . initialize ( type , bubbles , cancelable ) ;
} ;
var p = Event . prototype ;
2014-11-18 18:26:26 -05:00
Event . prototype . constructor = Event ;
2014-01-03 13:32:13 -05:00
// events:
// public properties:
/ * *
* The type of event .
* @ property type
* @ type String
* * /
p . type = null ;
/ * *
* The object that generated an event .
* @ property target
* @ type Object
* @ default null
* @ readonly
* /
p . target = null ;
/ * *
* The current target that a bubbling event is being dispatched from . For non - bubbling events , this will
* always be the same as target . For example , if childObj . parent = parentObj , and a bubbling event
* is generated from childObj , then a listener on parentObj would receive the event with
* target = childObj ( the original target ) and currentTarget = parentObj ( where the listener was added ) .
* @ property currentTarget
* @ type Object
* @ default null
* @ readonly
* /
p . currentTarget = null ;
/ * *
* For bubbling events , this indicates the current event phase : < OL >
* < LI > capture phase : starting from the top parent to the target < / L I >
* < LI > at target phase : currently being dispatched from the target < / L I >
* < LI > bubbling phase : from the target to the top parent < / L I >
* < / O L >
* @ property eventPhase
* @ type Number
* @ default 0
* @ readonly
* /
p . eventPhase = 0 ;
/ * *
* Indicates whether the event will bubble through the display list .
* @ property bubbles
* @ type Boolean
* @ default false
* @ readonly
* /
p . bubbles = false ;
/ * *
* Indicates whether the default behaviour of this event can be cancelled via
* { { # crossLink "Event/preventDefault" } } { { / c r o s s L i n k } } . T h i s i s s e t v i a t h e E v e n t c o n s t r u c t o r .
* @ property cancelable
* @ type Boolean
* @ default false
* @ readonly
* /
p . cancelable = false ;
/ * *
* The epoch time at which this event was created .
* @ property timeStamp
* @ type Number
* @ default 0
* @ readonly
* /
p . timeStamp = 0 ;
/ * *
* Indicates if { { # crossLink "Event/preventDefault" } } { { / c r o s s L i n k } } h a s b e e n c a l l e d
* on this event .
* @ property defaultPrevented
* @ type Boolean
* @ default false
* @ readonly
* /
p . defaultPrevented = false ;
/ * *
* Indicates if { { # crossLink "Event/stopPropagation" } } { { / c r o s s L i n k } } o r
* { { # crossLink "Event/stopImmediatePropagation" } } { { / c r o s s L i n k } } h a s b e e n c a l l e d o n t h i s e v e n t .
* @ property propagationStopped
* @ type Boolean
* @ default false
* @ readonly
* /
p . propagationStopped = false ;
/ * *
* Indicates if { { # crossLink "Event/stopImmediatePropagation" } } { { / c r o s s L i n k } } h a s b e e n c a l l e d
* on this event .
* @ property immediatePropagationStopped
* @ type Boolean
* @ default false
* @ readonly
* /
p . immediatePropagationStopped = false ;
/ * *
* Indicates if { { # crossLink "Event/remove" } } { { / c r o s s L i n k } } h a s b e e n c a l l e d o n t h i s e v e n t .
* @ property removed
* @ type Boolean
* @ default false
* @ readonly
* /
p . removed = false ;
// constructor:
/ * *
* Initialization method .
* @ method initialize
* @ param { String } type The event type .
* @ param { Boolean } bubbles Indicates whether the event will bubble through the display list .
* @ param { Boolean } cancelable Indicates whether the default behaviour of this event can be cancelled .
* @ protected
* * /
p . initialize = function ( type , bubbles , cancelable ) {
this . type = type ;
this . bubbles = bubbles ;
this . cancelable = cancelable ;
this . timeStamp = ( new Date ( ) ) . getTime ( ) ;
} ;
// public methods:
/ * *
* Sets { { # crossLink "Event/defaultPrevented" } } { { / c r o s s L i n k } } t o t r u e .
* Mirrors the DOM event standard .
* @ method preventDefault
* * /
p . preventDefault = function ( ) {
this . defaultPrevented = true ;
} ;
/ * *
* Sets { { # crossLink "Event/propagationStopped" } } { { / c r o s s L i n k } } t o t r u e .
* Mirrors the DOM event standard .
* @ method stopPropagation
* * /
p . stopPropagation = function ( ) {
this . propagationStopped = true ;
} ;
/ * *
* Sets { { # crossLink "Event/propagationStopped" } } { { / c r o s s L i n k } } a n d
* { { # crossLink "Event/immediatePropagationStopped" } } { { / c r o s s L i n k } } t o t r u e .
* Mirrors the DOM event standard .
* @ method stopImmediatePropagation
* * /
p . stopImmediatePropagation = function ( ) {
this . immediatePropagationStopped = this . propagationStopped = true ;
} ;
/ * *
* Causes the active listener to be removed via removeEventListener ( ) ;
*
* myBtn . addEventListener ( "click" , function ( evt ) {
* // do stuff...
* evt . remove ( ) ; // removes this listener.
* } ) ;
*
* @ method remove
* * /
p . remove = function ( ) {
this . removed = true ;
} ;
/ * *
* Returns a clone of the Event instance .
* @ method clone
* @ return { Event } a clone of the Event instance .
* * /
p . clone = function ( ) {
return new Event ( this . type , this . bubbles , this . cancelable ) ;
} ;
2014-11-18 18:26:26 -05:00
/ * *
* Provides a chainable shortcut method for setting a number of properties on the instance .
*
* @ method set
* @ param { Object } props A generic object containing properties to copy to the instance .
* @ return { Event } Returns the instance the method is called on ( useful for chaining calls . )
* /
p . set = function ( props ) {
for ( var n in props ) { this [ n ] = props [ n ] ; }
return this ;
} ;
2014-01-03 13:32:13 -05:00
/ * *
* Returns a string representation of this object .
* @ method toString
* @ return { String } a string representation of the instance .
* * /
p . toString = function ( ) {
return "[Event (type=" + this . type + ")]" ;
} ;
createjs . Event = Event ;
} ( ) ) ;
/ *
* IndexOf
* 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 .
* /
/ * *
* @ module CreateJS
* /
// namespace:
this . createjs = this . createjs || { } ;
/ * *
* @ class Utility Methods
* /
( function ( ) {
"use strict" ;
/ *
* Employs Duff ' s Device to make a more performant implementation of indexOf .
* see http : //jsperf.com/duffs-indexof/2
* # method indexOf
* @ param { Array } array Array to search for searchElement
* @ param searchElement Element to search array for .
* @ return { Number } The position of the first occurrence of a specified value searchElement in the passed in array ar .
* @ constructor
* /
/ * r e p l a c e d w i t h s i m p l e f o r l o o p f o r n o w , p e r h a p s w i l l b e r e s e a r c h e d f u r t h e r
createjs . indexOf = function ( ar , searchElement ) {
var l = ar . length ;
var n = ( l * 0.125 ) ^ 0 ; // 0.125 == 1/8, using multiplication because it's faster in some browsers // ^0 floors result
for ( var i = 0 ; i < n ; i ++ ) {
if ( searchElement === ar [ i * 8 ] ) { return ( i * 8 ) ; }
if ( searchElement === ar [ i * 8 + 1 ] ) { return ( i * 8 + 1 ) ; }
if ( searchElement === ar [ i * 8 + 2 ] ) { return ( i * 8 + 2 ) ; }
if ( searchElement === ar [ i * 8 + 3 ] ) { return ( i * 8 + 3 ) ; }
if ( searchElement === ar [ i * 8 + 4 ] ) { return ( i * 8 + 4 ) ; }
if ( searchElement === ar [ i * 8 + 5 ] ) { return ( i * 8 + 5 ) ; }
if ( searchElement === ar [ i * 8 + 6 ] ) { return ( i * 8 + 6 ) ; }
if ( searchElement === ar [ i * 8 + 7 ] ) { return ( i * 8 + 7 ) ; }
}
var n = l % 8 ;
for ( var i = 0 ; i < n ; i ++ ) {
if ( searchElement === ar [ l - n + i ] ) {
return l - n + i ;
}
}
return - 1 ;
}
* /
/ * *
* Finds the first occurrence of a specified value searchElement in the passed in array , and returns the index of
* that value . Returns - 1 if value is not found .
*
* var i = createjs . indexOf ( myArray , myElementToFind ) ;
*
* @ method indexOf
* @ param { Array } array Array to search for searchElement
* @ param searchElement Element to find in array .
* @ return { Number } The first index of searchElement in array .
* /
createjs . indexOf = function ( array , searchElement ) {
for ( var i = 0 , l = array . length ; i < l ; i ++ ) {
if ( searchElement === array [ i ] ) {
return i ;
}
}
return - 1 ;
}
} ( ) ) ; / *
* Proxy
* 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 .
* /
/ * *
* @ module CreateJS
* /
// namespace:
this . createjs = this . createjs || { } ;
/ * *
* Various utilities that the CreateJS Suite uses . Utilities are created as separate files , and will be available on the
* createjs namespace directly :
*
* < h4 > Example < / h 4 >
* myObject . addEventListener ( "change" , createjs . proxy ( myMethod , scope ) ) ;
*
* @ class Utility Methods
* @ main Utility Methods
* /
( function ( ) {
"use strict" ;
/ * *
* A function proxy for methods . By default , JavaScript methods do not maintain scope , so passing a method as a
* callback will result in the method getting called in the scope of the caller . Using a proxy ensures that the
* method gets called in the correct scope .
*
* Additional arguments can be passed that will be applied to the function when it is called .
*
* < h4 > Example < / h 4 >
* myObject . addEventListener ( "event" , createjs . proxy ( myHandler , this , arg1 , arg2 ) ) ;
*
* function myHandler ( arg1 , arg2 ) {
* // This gets called when myObject.myCallback is executed.
* }
*
* @ method proxy
* @ param { Function } method The function to call
* @ param { Object } scope The scope to call the method name on
* @ param { mixed } [ arg ] * Arguments that are appended to the callback for additional params .
* @ public
* @ static
* /
createjs . proxy = function ( method , scope ) {
var aArgs = Array . prototype . slice . call ( arguments , 2 ) ;
return function ( ) {
return method . apply ( scope , Array . prototype . slice . call ( arguments , 0 ) . concat ( aArgs ) ) ;
} ;
}
2014-11-18 18:26:26 -05:00
} ( ) ) ; / *
* defineProperty
* 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 .
* /
/ * *
* @ module CreateJS
* /
// namespace:
this . createjs = this . createjs || { } ;
/ * *
* @ class Utility Methods
* /
( function ( ) {
"use strict" ;
/ * *
* Boolean value indicating if Object . defineProperty is supported .
*
* @ property definePropertySupported
* @ type { Boolean }
* @ default true
* /
var t = Object . defineProperty ? true : false ;
// IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors
var foo = { } ;
try {
Object . defineProperty ( foo , "bar" , {
get : function ( ) {
return this . _bar ;
} ,
set : function ( value ) {
this . _bar = value ;
}
} ) ;
} catch ( e ) {
t = false ;
} ;
createjs . definePropertySupported = t ;
2014-01-03 13:32:13 -05:00
} ( ) ) ; / *
* Sound
* 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 .
* /
// namespace:
this . createjs = this . createjs || { } ;
/ * *
* The SoundJS library manages the playback of audio on the web . It works via plugins which abstract the actual audio
* implementation , so playback is possible on any platform without specific knowledge of what mechanisms are necessary
* to play sounds .
*
* To use SoundJS , use the public API on the { { # crossLink "Sound" } } { { / c r o s s L i n k } } c l a s s . T h i s A P I i s f o r :
* < ul > < li > Installing audio playback Plugins < / l i >
* < li > Registering ( and preloading ) sounds < / l i >
* < li > Creating and playing sounds < / l i >
* < li > Master volume , mute , and stop controls for all sounds at once < / l i >
* < / u l >
*
* < b > Controlling Sounds < /b><br / >
* Playing sounds creates { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } i n s t a n c e s , w h i c h c a n b e c o n t r o l l e d i n d i v i d u a l l y .
* < ul > < li > Pause , resume , seek , and stop sounds < / l i >
* < li > Control a sound ' s volume , mute , and pan < / l i >
* < li > Listen for events on sound instances to get notified when they finish , loop , or fail < / l i >
* < / u l >
*
* < h4 > Feature Set Example < / h 4 >
* createjs . Sound . alternateExtensions = [ "mp3" ] ;
* createjs . Sound . addEventListener ( "fileload" , createjs . proxy ( this . loadHandler , this ) ) ;
* createjs . Sound . registerSound ( "path/to/mySound.ogg" , "sound" ) ;
* function loadHandler ( event ) {
* // This is fired for each sound that is registered.
* var instance = createjs . Sound . play ( "sound" ) ; // play using id. Could also use full sourcepath or event.src.
* instance . addEventListener ( "complete" , createjs . proxy ( this . handleComplete , this ) ) ;
* instance . volume = 0.5 ;
* }
*
* < h4 > Browser Support < / h 4 >
2014-11-18 18:26:26 -05:00
* Audio will work in browsers which support WebAudio ( < a href = "http://caniuse.com/audio-api" > http : //caniuse.com/audio-api</a>)
* or HTMLAudioElement ( < a href = "http://caniuse.com/audio" > http : //caniuse.com/audio</a>). A Flash fallback can be added
2014-01-03 13:32:13 -05:00
* as well , which will work in any browser that supports the Flash player .
* @ module SoundJS
* @ main SoundJS
* /
( function ( ) {
"use strict" ;
/ * *
* The Sound class is the public API for creating sounds , controlling the overall sound levels , and managing plugins .
* All Sound APIs on this class are static .
*
* < b > Registering and Preloading < /b><br / >
* Before you can play a sound , it < b > must < / b > b e r e g i s t e r e d . Y o u c a n d o t h i s w i t h { { # c r o s s L i n k " S o u n d / r e g i s t e r S o u n d " } } { { / c r o s s L i n k } } ,
* or register multiple sounds using { { # crossLink "Sound/registerManifest" } } { { / c r o s s L i n k } } . I f y o u d o n ' t r e g i s t e r a
* sound prior to attempting to play it using { { # crossLink "Sound/play" } } { { / c r o s s L i n k } } o r c r e a t e i t u s i n g { { # c r o s s L i n k " S o u n d / c r e a t e I n s t a n c e " } } { { / c r o s s L i n k } } ,
* the sound source will be automatically registered but playback will fail as the source will not be ready . If you use
2014-11-18 18:26:26 -05:00
* < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > , r e g i s t r a t i o n i s h a n d l e d f o r y o u w h e n t h e s o u n d i s
2014-01-03 13:32:13 -05:00
* preloaded . It is recommended to preload sounds either internally using the register functions or externally using
* PreloadJS so they are ready when you want to use them .
*
* < b > Playback < /b><br / >
* To play a sound once it ' s been registered and preloaded , use the { { # crossLink "Sound/play" } } { { / c r o s s L i n k } } m e t h o d .
* This method returns a { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } w h i c h c a n b e p a u s e d , r e s u m e d , m u t e d , e t c .
* Please see the { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } d o c u m e n t a t i o n f o r m o r e o n t h e i n s t a n c e c o n t r o l A P I s .
*
* < b > Plugins < /b><br / >
* By default , the { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } o r t h e { { # c r o s s L i n k " H T M L A u d i o P l u g i n " } } { { / c r o s s L i n k } }
* are used ( when available ) , although developers can change plugin priority or add new plugins ( such as the
* provided { { # crossLink "FlashPlugin" } } { { / c r o s s L i n k } } ) . P l e a s e s e e t h e { { # c r o s s L i n k " S o u n d " } } { { / c r o s s L i n k } } A P I
* methods for more on the playback and plugin APIs . To install plugins , or specify a different plugin order , see
* { { # crossLink "Sound/installPlugins" } } { { / c r o s s L i n k } } .
*
* < h4 > Example < / h 4 >
* createjs . Sound . registerPlugins ( [ createjs . WebAudioPlugin , createjs . FlashPlugin ] ) ;
* createjs . Sound . alternateExtensions = [ "mp3" ] ;
* createjs . Sound . addEventListener ( "fileload" , createjs . proxy ( this . loadHandler , ( this ) ) ;
* createjs . Sound . registerSound ( "path/to/mySound.ogg" , "sound" ) ;
* function loadHandler ( event ) {
* // This is fired for each sound that is registered.
* var instance = createjs . Sound . play ( "sound" ) ; // play using id. Could also use full source path or event.src.
* instance . addEventListener ( "complete" , createjs . proxy ( this . handleComplete , this ) ) ;
* instance . volume = 0.5 ;
* }
*
* The maximum number of concurrently playing instances of the same sound can be specified in the "data" argument
* of { { # crossLink "Sound/registerSound" } } { { / c r o s s L i n k } } . N o t e t h a t i f n o t s p e c i f i e d , t h e a c t i v e p l u g i n w i l l a p p l y
* a default limit . Currently HTMLAudioPlugin sets a default limit of 2 , while WebAudioPlugin and FlashPlugin set a
* default limit of 100.
*
* createjs . Sound . registerSound ( "sound.mp3" , "soundId" , 4 ) ;
*
* Sound can be used as a plugin with PreloadJS to help preload audio properly . Audio preloaded with PreloadJS is
* automatically registered with the Sound class . When audio is not preloaded , Sound will do an automatic internal
2014-11-18 18:26:26 -05:00
* load . As a result , it may fail to play the first time play is called if the audio is not finished loading . Use the
2014-01-03 13:32:13 -05:00
* { { # crossLink "Sound/fileload" } } { { / c r o s s L i n k } } e v e n t t o d e t e r m i n e w h e n a s o u n d h a s f i n i s h e d i n t e r n a l l y p r e l o a d i n g .
* It is recommended that all audio is preloaded before it is played .
*
2014-11-18 18:26:26 -05:00
* var queue = new createjs . LoadQueue ( ) ;
* queue . installPlugin ( createjs . Sound ) ;
*
* < b > Audio Sprites < /b><br / >
* SoundJS has added support for Audio Sprites , available as of version 0.5 . 3.
* For those unfamiliar with audio sprites , they are much like CSS sprites or sprite sheets : multiple audio assets
* grouped into a single file .
*
* Benefits of Audio Sprites
* < ul > < li > More robust support for older browsers and devices that only allow a single audio instance , such as iOS 5. < / l i >
* < li > They provide a work around for the Internet Explorer 9 audio tag limit , which until now restricted how many
* different sounds we could load at once . < / l i >
* < li > Faster loading by only requiring a single network request for several sounds , especially on mobile devices
* where the network round trip for each file can add significant latency . < / l i > < / u l >
*
* Drawbacks of Audio Sprites
* < ul > < li > No guarantee of smooth looping when using HTML or Flash audio . If you have a track that needs to loop
* smoothly and you are supporting non - web audio browsers , do not use audio sprites for that sound if you can avoid it . < / l i >
* < li > No guarantee that HTML audio will play back immediately , especially the first time . In some browsers ( Chrome ! ) ,
* HTML audio will only load enough to play through – so we rely on the “ canplaythrough ” event to determine if the audio is loaded .
* Since audio sprites must jump ahead to play specific sounds , the audio may not yet have downloaded . < / l i >
* < li > Audio sprites share the same core source , so if you have a sprite with 5 sounds and are limited to 2
* concurrently playing instances , that means you can only play 2 of the sounds at the same time . < / l i > < / u l >
*
* < h4 > Example < / h 4 >
* createjs . Sound . initializeDefaultPlugins ( ) ;
* var assetsPath = "./assets/" ;
* var manifest = [ {
* src : "MyAudioSprite.ogg" , data : {
* audioSprite : [
* { id : "sound1" , startTime : 0 , duration : 500 } ,
* { id : "sound2" , startTime : 1000 , duration : 400 } ,
* { id : "sound3" , startTime : 1700 , duration : 1000 }
* ] }
* }
* ] ;
* createjs . Sound . alternateExtensions = [ "mp3" ] ;
* createjs . Sound . addEventListener ( "fileload" , loadSound ) ;
* createjs . Sound . registerManifest ( manifest , assetsPath ) ;
* // after load is complete
* createjs . Sound . play ( "sound2" ) ;
*
* You can also create audio sprites on the fly by setting the startTime and duration when creating an new SoundInstance .
*
* createjs . Sound . play ( "MyAudioSprite" , { startTime : 1000 , duration : 400 } ) ;
2014-01-03 13:32:13 -05:00
*
* < b > Mobile Safe Approach < /b><br / >
* Mobile devices require sounds to be played inside of a user initiated event ( touch / click ) in varying degrees .
* As of SoundJS 0.4 . 1 , you can launch a site inside of a user initiated event and have audio playback work . To
* enable as broadly as possible , the site needs to setup the Sound plugin in its initialization ( for example via
* < code > createjs . Sound . initializeDefaultPlugins ( ) ; < / c o d e > ) , a n d a l l s o u n d s n e e d t o b e p l a y e d i n t h e s c o p e o f t h e
* application . See the MobileSafe demo for a working example .
*
* < h4 > Example < / h 4 >
* document . getElementById ( "status" ) . addEventListener ( "click" , handleTouch , false ) ; // works on Android and iPad
* function handleTouch ( event ) {
* document . getElementById ( "status" ) . removeEventListener ( "click" , handleTouch , false ) ; // remove the listener
* var thisApp = new myNameSpace . MyApp ( ) ; // launch the app
* }
*
* < h4 > Known Browser and OS issues < / h 4 >
* < b > IE 9 HTML Audio limitations < /b><br / >
* < ul > < li > There is a delay in applying volume changes to tags that occurs once playback is started . So if you have
* muted all sounds , they will all play during this delay until the mute applies internally . This happens regardless of
* when or how you apply the volume change , as the tag seems to need to play to apply it . < / l i >
* < li > MP3 encoding will not always work for audio tags , particularly in Internet Explorer . We ' ve found default
* encoding with 64 kbps works . < / l i >
2014-11-18 18:26:26 -05:00
* < li > Occasionally very short samples will get cut off . < / l i >
2014-01-03 13:32:13 -05:00
* < li > There is a limit to how many audio tags you can load and play at once , which appears to be determined by
* hardware and browser settings . See { { # crossLink "HTMLAudioPlugin.MAX_INSTANCES" } } { { / c r o s s L i n k } } f o r a s a f e e s t i m a t e . < / l i > < / u l >
*
* < b > Firefox 25 Web Audio limitations < / b >
* < ul > < li > mp3 audio files do not load properly on all windows machines , reported
* < a href = "https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target = "_blank" > here < / a > . < / b r >
2014-02-02 19:31:06 -05:00
* For this reason it is recommended to pass another FF supported type ( ie ogg ) first until this bug is resolved , if possible . < / l i > < / u l >
2014-01-03 13:32:13 -05:00
* < b > Safari limitations < /b><br / >
* < ul > < li > Safari requires Quicktime to be installed for audio playback . < / l i > < / u l >
*
* < b > iOS 6 Web Audio limitations < /b><br / >
* < ul > < li > Sound is initially muted and will only unmute through play being called inside a user initiated event
* ( touch / click ) . < / l i >
2014-02-02 19:31:06 -05:00
* < li > A bug exists that will distort un - cached web audio when a video element is present in the DOM that has audio at a different sampleRate . < / l i >
2014-01-03 13:32:13 -05:00
* < li > Note HTMLAudioPlugin is not supported on iOS by default . See { { # crossLink "HTMLAudioPlugin" } } { { / c r o s s L i n k } }
2014-02-02 19:31:06 -05:00
* for more details . < / l i >
2014-01-03 13:32:13 -05:00
* < / u l >
*
* < b > Android HTML Audio limitations < /b><br / >
* < ul > < li > We have no control over audio volume . Only the user can set volume on their device . < / l i >
* < li > We can only play audio inside a user event ( touch / click ) . This currently means you cannot loop sound or use
* a delay . < / l i > < / u l >
*
*
* @ class Sound
* @ static
* @ uses EventDispatcher
* /
function Sound ( ) {
throw "Sound cannot be instantiated" ;
}
var s = Sound ;
2014-11-18 18:26:26 -05:00
// TODO DEPRECATED
2014-01-03 13:32:13 -05:00
/ * *
2014-11-18 18:26:26 -05:00
* REMOVED
* Use { { # crossLink "Sound/alternateExtensions:property" } } { { / c r o s s L i n k } } i n s t e a d
2014-01-03 13:32:13 -05:00
* @ property DELIMITER
* @ type { String }
* @ default |
* @ static
* @ deprecated
* /
/ * *
* The interrupt value to interrupt any currently playing instance with the same source , if the maximum number of
* instances of the sound are already playing .
* @ property INTERRUPT _ANY
* @ type { String }
* @ default any
* @ static
* /
s . INTERRUPT _ANY = "any" ;
/ * *
* The interrupt value to interrupt the earliest currently playing instance with the same source that progressed the
* least distance in the audio track , if the maximum number of instances of the sound are already playing .
* @ property INTERRUPT _EARLY
* @ type { String }
* @ default early
* @ static
* /
s . INTERRUPT _EARLY = "early" ;
/ * *
* The interrupt value to interrupt the currently playing instance with the same source that progressed the most
* distance in the audio track , if the maximum number of instances of the sound are already playing .
* @ property INTERRUPT _LATE
* @ type { String }
* @ default late
* @ static
* /
s . INTERRUPT _LATE = "late" ;
/ * *
* The interrupt value to not interrupt any currently playing instances with the same source , if the maximum number of
* instances of the sound are already playing .
* @ property INTERRUPT _NONE
* @ type { String }
* @ default none
* @ static
* /
s . INTERRUPT _NONE = "none" ;
// The playState in plugins should be implemented with these values.
/ * *
* Defines the playState of an instance that is still initializing .
* @ property PLAY _INITED
* @ type { String }
* @ default playInited
* @ static
* /
s . PLAY _INITED = "playInited" ;
/ * *
* Defines the playState of an instance that is currently playing or paused .
* @ property PLAY _SUCCEEDED
* @ type { String }
* @ default playSucceeded
* @ static
* /
s . PLAY _SUCCEEDED = "playSucceeded" ;
/ * *
* Defines the playState of an instance that was interrupted by another instance .
* @ property PLAY _INTERRUPTED
* @ type { String }
* @ default playInterrupted
* @ static
* /
s . PLAY _INTERRUPTED = "playInterrupted" ;
/ * *
* Defines the playState of an instance that completed playback .
* @ property PLAY _FINISHED
* @ type { String }
* @ default playFinished
* @ static
* /
s . PLAY _FINISHED = "playFinished" ;
/ * *
* Defines the playState of an instance that failed to play . This is usually caused by a lack of available channels
* when the interrupt mode was "INTERRUPT_NONE" , the playback stalled , or the sound could not be found .
* @ property PLAY _FAILED
* @ type { String }
* @ default playFailed
* @ static
* /
s . PLAY _FAILED = "playFailed" ;
/ * *
* A list of the default supported extensions that Sound will < i > try < / i > t o p l a y . P l u g i n s w i l l c h e c k i f t h e b r o w s e r
* can play these types , so modifying this list before a plugin is initialized will allow the plugins to try to
* support additional media types .
*
* NOTE this does not currently work for { { # crossLink "FlashPlugin" } } { { / c r o s s L i n k } } .
*
2014-02-02 19:31:06 -05:00
* More details on file formats can be found at < a href = "http://en.wikipedia.org/wiki/Audio_file_format" target = "_blank" > http : //en.wikipedia.org/wiki/Audio_file_format</a>.<br />
* A very detailed list of file formats can be found at < a href = "http://www.fileinfo.com/filetypes/audio" target = "_blank" > http : //www.fileinfo.com/filetypes/audio</a>.
2014-01-03 13:32:13 -05:00
* @ property SUPPORTED _EXTENSIONS
* @ type { Array [ String ] }
* @ default [ "mp3" , "ogg" , "mpeg" , "wav" , "m4a" , "mp4" , "aiff" , "wma" , "mid" ]
2014-02-02 19:31:06 -05:00
* @ since 0.4 . 0
2014-01-03 13:32:13 -05:00
* /
2014-11-18 18:26:26 -05:00
s . SUPPORTED _EXTENSIONS = [ "mp3" , "ogg" , "mpeg" , "wav" , "m4a" , "mp4" , "aiff" , "wma" , "mid" ] ;
2014-01-03 13:32:13 -05:00
/ * *
* Some extensions use another type of extension support to play ( one of them is a codex ) . This allows you to map
* that support so plugins can accurately determine if an extension is supported . Adding to this list can help
* plugins determine more accurately if an extension is supported .
2014-02-02 19:31:06 -05:00
*
* A useful list of extensions for each format can be found at < a href = "http://html5doctor.com/html5-audio-the-state-of-play/" target = "_blank" > http : //html5doctor.com/html5-audio-the-state-of-play/</a>.
2014-01-03 13:32:13 -05:00
* @ property EXTENSION _MAP
* @ type { Object }
* @ since 0.4 . 0
2014-02-02 19:31:06 -05:00
* @ default { m4a : "mp4" }
2014-01-03 13:32:13 -05:00
* /
s . EXTENSION _MAP = {
m4a : "mp4"
} ;
/ * *
* The RegExp pattern used to parse file URIs . This supports simple file names , as well as full domain URIs with
* query strings . The resulting match is : protocol : $1 domain : $2 path : $3 file : $4 extension : $5 query : $6 .
* @ property FILE _PATTERN
* @ type { RegExp }
* @ static
* @ protected
* /
s . FILE _PATTERN = /^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/ ;
/ * *
* Determines the default behavior for interrupting other currently playing instances with the same source , if the
* maximum number of instances of the sound are already playing . Currently the default is { { # crossLink "Sound/INTERRUPT_NONE:property" } } { { / c r o s s L i n k } }
* but this can be set and will change playback behavior accordingly . This is only used when { { # crossLink "Sound/play" } } { { / c r o s s L i n k } }
* is called without passing a value for interrupt .
* @ property defaultInterruptBehavior
* @ type { String }
2014-02-02 19:31:06 -05:00
* @ default Sound . INTERRUPT _NONE , or "none"
2014-01-03 13:32:13 -05:00
* @ static
* @ since 0.4 . 0
* /
s . defaultInterruptBehavior = s . INTERRUPT _NONE ; // OJR does s.INTERRUPT_ANY make more sense as default? Needs game dev testing to see which case makes more sense.
/ * *
* An array of extensions to attempt to use when loading sound , if the default is unsupported by the active plugin .
* These are applied in order , so if you try to Load Thunder . ogg in a browser that does not support ogg , and your
2014-02-02 19:31:06 -05:00
* extensions array is [ "mp3" , "m4a" , "wav" ] it will check mp3 support , then m4a , then wav . The audio files need
* to exist in the same location , as only the extension is altered .
2014-01-03 13:32:13 -05:00
*
2014-02-02 19:31:06 -05:00
* Note that regardless of which file is loaded , you can call { { # crossLink "Sound/createInstance" } } { { / c r o s s L i n k } }
* and { { # crossLink "Sound/play" } } { { / c r o s s L i n k } } u s i n g t h e s a m e i d o r f u l l s o u r c e p a t h p a s s e d f o r l o a d i n g .
2014-01-03 13:32:13 -05:00
* < h4 > Example < / h 4 >
2014-02-02 19:31:06 -05:00
* var manifest = [
* { src : "myPath/mySound.ogg" , id : "example" } ,
* ] ;
* createjs . Sound . alternateExtensions = [ "mp3" ] ; // now if ogg is not supported, SoundJS will try asset0.mp3
* createjs . Sound . addEventListener ( "fileload" , handleLoad ) ; // call handleLoad when each sound loads
* createjs . Sound . registerManifest ( manifest , assetPath ) ;
* // ...
* createjs . Sound . play ( "myPath/mySound.ogg" ) ; // works regardless of what extension is supported. Note calling with ID is a better approach
2014-01-03 13:32:13 -05:00
*
* @ property alternateExtensions
* @ type { Array }
* @ since 0.5 . 2
* /
s . alternateExtensions = [ ] ;
/ * *
* Used internally to assign unique IDs to each SoundInstance .
2014-02-02 19:31:06 -05:00
* @ property _lastID
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ static
* @ protected
* /
2014-02-02 19:31:06 -05:00
s . _lastID = 0 ;
2014-01-03 13:32:13 -05:00
/ * *
* The currently active plugin . If this is null , then no plugin could be initialized . If no plugin was specified ,
* Sound attempts to apply the default plugins : { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } , f o l l o w e d b y
* { { # crossLink "HTMLAudioPlugin" } } { { / c r o s s L i n k } } .
* @ property activePlugin
* @ type { Object }
* @ static
* /
s . activePlugin = null ;
/ * *
* Determines if the plugins have been registered . If false , the first call to play ( ) will instantiate the default
* plugins ( { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } , f o l l o w e d b y { { # c r o s s L i n k " H T M L A u d i o P l u g i n " } } { { / c r o s s L i n k } } ) .
* If plugins have been registered , but none are applicable , then sound playback will fail .
2014-02-02 19:31:06 -05:00
* @ property _pluginsRegistered
2014-01-03 13:32:13 -05:00
* @ type { Boolean }
* @ default false
* @ static
* @ protected
* /
2014-02-02 19:31:06 -05:00
s . _pluginsRegistered = false ;
2014-01-03 13:32:13 -05:00
/ * *
* The master volume value , which affects all sounds . Use { { # crossLink "Sound/getVolume" } } { { / c r o s s L i n k } } a n d
* { { # crossLink "Sound/setVolume" } } { { / c r o s s L i n k } } t o m o d i f y t h e v o l u m e o f a l l a u d i o .
2014-02-02 19:31:06 -05:00
* @ property _masterVolume
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 1
* @ protected
* @ since 0.4 . 0
* /
2014-02-02 19:31:06 -05:00
s . _masterVolume = 1 ;
2014-01-03 13:32:13 -05:00
/ * *
* The master mute value , which affects all sounds . This is applies to all sound instances . This value can be set
* through { { # crossLink "Sound/setMute" } } { { / c r o s s L i n k } } a n d a c c e s s e d v i a { { # c r o s s L i n k " S o u n d / g e t M u t e " } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* @ property _masterMute
2014-01-03 13:32:13 -05:00
* @ type { Boolean }
* @ default false
* @ protected
* @ static
* @ since 0.4 . 0
* /
2014-02-02 19:31:06 -05:00
s . _masterMute = false ;
2014-01-03 13:32:13 -05:00
/ * *
* An array containing all currently playing instances . This allows Sound to control the volume , mute , and playback of
* all instances when using static APIs like { { # crossLink "Sound/stop" } } { { / c r o s s L i n k } } a n d { { # c r o s s L i n k " S o u n d / s e t V o l u m e " } } { { / c r o s s L i n k } } .
* When an instance has finished playback , it gets removed via the { { # crossLink "Sound/finishedPlaying" } } { { / c r o s s L i n k } }
2014-02-02 19:31:06 -05:00
* method . If the user replays an instance , it gets added back in via the { { # crossLink "Sound/_beginPlaying" } } { { / c r o s s L i n k } }
2014-01-03 13:32:13 -05:00
* method .
2014-02-02 19:31:06 -05:00
* @ property _instances
2014-01-03 13:32:13 -05:00
* @ type { Array }
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _instances = [ ] ;
2014-01-03 13:32:13 -05:00
/ * *
2014-11-18 18:26:26 -05:00
* An object hash storing objects with sound sources , startTime , and duration via there corresponding ID .
2014-02-02 19:31:06 -05:00
* @ property _idHash
2014-01-03 13:32:13 -05:00
* @ type { Object }
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _idHash = { } ;
2014-01-03 13:32:13 -05:00
/ * *
* An object hash that stores preloading sound sources via the parsed source that is passed to the plugin . Contains the
* source , id , and data that was passed in by the user . Parsed sources can contain multiple instances of source , id ,
* and data .
2014-02-02 19:31:06 -05:00
* @ property _preloadHash
2014-01-03 13:32:13 -05:00
* @ type { Object }
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _preloadHash = { } ;
2014-01-03 13:32:13 -05:00
/ * *
* An object that stands in for audio that fails to play . This allows developers to continue to call methods
* on the failed instance without having to check if it is valid first . The instance is instantiated once , and
* shared to keep the memory footprint down .
2014-02-02 19:31:06 -05:00
* @ property _defaultSoundInstance
2014-01-03 13:32:13 -05:00
* @ type { Object }
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _defaultSoundInstance = null ;
2014-01-03 13:32:13 -05:00
// mix-ins:
// EventDispatcher methods:
s . addEventListener = null ;
s . removeEventListener = null ;
s . removeAllEventListeners = null ;
s . dispatchEvent = null ;
s . hasEventListener = null ;
s . _listeners = null ;
createjs . EventDispatcher . initialize ( s ) ; // inject EventDispatcher methods.
// Events
/ * *
* This event is fired when a file finishes loading internally . This event is fired for each loaded sound ,
* so any handler methods should look up the < code > event . src < / c o d e > t o h a n d l e a p a r t i c u l a r s o u n d .
* @ event fileload
* @ param { Object } target The object that dispatched the event .
* @ param { String } type The event type .
* @ param { String } src The source of the sound that was loaded .
* @ param { String } [ id ] The id passed in when the sound was registered . If one was not provided , it will be null .
* @ param { Number | Object } [ data ] Any additional data associated with the item . If not provided , it will be undefined .
* @ since 0.4 . 1
* /
/ * *
* Used by external plugins to dispatch file load events .
2014-02-02 19:31:06 -05:00
* @ method _sendFileLoadEvent
2014-01-03 13:32:13 -05:00
* @ param { String } src A sound file has completed loading , and should be dispatched .
* @ protected
* @ static
* @ since 0.4 . 1
* /
2014-02-02 19:31:06 -05:00
s . _sendFileLoadEvent = function ( src ) {
2014-11-18 18:26:26 -05:00
if ( ! s . _preloadHash [ src ] ) { return ; }
2014-02-02 19:31:06 -05:00
for ( var i = 0 , l = s . _preloadHash [ src ] . length ; i < l ; i ++ ) {
var item = s . _preloadHash [ src ] [ i ] ;
s . _preloadHash [ src ] [ i ] = true ;
2014-01-03 13:32:13 -05:00
if ( ! s . hasEventListener ( "fileload" ) ) { continue ; }
var event = new createjs . Event ( "fileload" ) ;
event . src = item . src ;
event . id = item . id ;
event . data = item . data ;
2014-11-18 18:26:26 -05:00
event . sprite = item . sprite ;
2014-01-03 13:32:13 -05:00
s . dispatchEvent ( event ) ;
}
} ;
/ * *
* Get the preload rules to allow Sound to be used as a plugin by < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > .
* Any load calls that have the matching type or extension will fire the callback method , and use the resulting
* object , which is potentially modified by Sound . This helps when determining the correct path , as well as
* registering the audio instance ( s ) with Sound . This method should not be called , except by PreloadJS .
* @ method getPreloadHandlers
* @ return { Object } An object containing :
* < ul > < li > callback : A preload callback that is fired when a file is added to PreloadJS , which provides
* Sound a mechanism to modify the load parameters , select the correct file format , register the sound , etc . < / l i >
* < li > types : A list of file types that are supported by Sound ( currently supports "sound" ) . < / l i >
* < li > extensions : A list of file extensions that are supported by Sound ( see { { # crossLink "Sound.SUPPORTED_EXTENSIONS" } } { { / c r o s s L i n k } } ) . < / l i > < / u l >
* @ static
* @ protected
* /
s . getPreloadHandlers = function ( ) {
return {
callback : createjs . proxy ( s . initLoad , s ) ,
types : [ "sound" ] ,
extensions : s . SUPPORTED _EXTENSIONS
} ;
} ;
/ * *
2014-11-18 18:26:26 -05:00
* Used by { { # crossLink "Sound/registerPlugins" } } { { / c r o s s L i n k } } t o r e g i s t e r a S o u n d p l u g i n .
2014-01-03 13:32:13 -05:00
*
* @ method _registerPlugin
* @ param { Object } plugin The plugin class to install .
* @ return { Boolean } Whether the plugin was successfully initialized .
* @ static
* @ private
* /
s . _registerPlugin = function ( plugin ) {
// Note: Each plugin is passed in as a class reference, but we store the activePlugin as an instance
if ( plugin . isSupported ( ) ) {
s . activePlugin = new plugin ( ) ;
return true ;
}
return false ;
} ;
/ * *
* Register a list of Sound plugins , in order of precedence . To register a single plugin , pass a single element in the array .
*
* < h4 > Example < / h 4 >
* createjs . FlashPlugin . swfPath = "../src/SoundJS/" ;
* createjs . Sound . registerPlugins ( [ createjs . WebAudioPlugin , createjs . HTMLAudioPlugin , createjs . FlashPlugin ] ) ;
*
* @ method registerPlugins
* @ param { Array } plugins An array of plugins classes to install .
* @ return { Boolean } Whether a plugin was successfully initialized .
* @ static
* /
s . registerPlugins = function ( plugins ) {
2014-11-18 18:26:26 -05:00
s . _pluginsRegistered = true ;
2014-01-03 13:32:13 -05:00
for ( var i = 0 , l = plugins . length ; i < l ; i ++ ) {
2014-11-18 18:26:26 -05:00
if ( s . _registerPlugin ( plugins [ i ] ) ) {
2014-01-03 13:32:13 -05:00
return true ;
}
}
return false ;
} ;
/ * *
* Initialize the default plugins . This method is automatically called when any audio is played or registered before
* the user has manually registered plugins , and enables Sound to work without manual plugin setup . Currently , the
* default plugins are { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } f o l l o w e d b y { { # c r o s s L i n k " H T M L A u d i o P l u g i n " } } { { / c r o s s L i n k } } .
*
2014-02-02 19:31:06 -05:00
* < h4 > Example < / h 4 >
* if ( ! createjs . initializeDefaultPlugins ( ) ) { return ; }
2014-01-03 13:32:13 -05:00
*
* @ method initializeDefaultPlugins
2014-02-02 19:31:06 -05:00
* @ returns { Boolean } True if a plugin was initialized , false otherwise .
2014-01-03 13:32:13 -05:00
* @ since 0.4 . 0
* /
s . initializeDefaultPlugins = function ( ) {
2014-11-18 18:26:26 -05:00
if ( s . activePlugin != null ) { return true ; }
if ( s . _pluginsRegistered ) { return false ; }
if ( s . registerPlugins ( [ createjs . WebAudioPlugin , createjs . HTMLAudioPlugin ] ) ) { return true ; }
2014-01-03 13:32:13 -05:00
return false ;
} ;
/ * *
* Determines if Sound has been initialized , and a plugin has been activated .
*
* < h4 > Example < / h 4 >
* This example sets up a Flash fallback , but only if there is no plugin specified yet .
*
2014-02-02 19:31:06 -05:00
* if ( ! createjs . Sound . isReady ( ) ) {
* createjs . FlashPlugin . swfPath = "../src/SoundJS/" ;
* createjs . Sound . registerPlugins ( [ createjs . WebAudioPlugin , createjs . HTMLAudioPlugin , createjs . FlashPlugin ] ) ;
* }
2014-01-03 13:32:13 -05:00
*
* @ method isReady
* @ return { Boolean } If Sound has initialized a plugin .
* @ static
* /
s . isReady = function ( ) {
return ( s . activePlugin != null ) ;
} ;
/ * *
* Get the active plugins capabilities , which help determine if a plugin can be used in the current environment ,
* or if the plugin supports a specific feature . Capabilities include :
* < ul >
* < li > < b > panning : < / b > I f t h e p l u g i n c a n p a n a u d i o f r o m l e f t t o r i g h t < / l i >
* < li > < b > volume ; < / b > I f t h e p l u g i n c a n c o n t r o l a u d i o v o l u m e . < / l i >
2014-02-02 19:31:06 -05:00
* < li > < b > tracks : < / b > T h e m a x i m u m n u m b e r o f a u d i o t r a c k s t h a t c a n b e p l a y e d b a c k a t a t i m e . T h i s w i l l b e - 1
* if there is no known limit . < / l i >
* < br / > An entry for each file type in { { # crossLink "Sound/SUPPORTED_EXTENSIONS:property" } } { { / c r o s s L i n k } } :
2014-01-03 13:32:13 -05:00
* < li > < b > mp3 : < / b > I f M P 3 a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > ogg : < / b > I f O G G a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > wav : < / b > I f W A V a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > mpeg : < / b > I f M P E G a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > m4a : < / b > I f M 4 A a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > mp4 : < / b > I f M P 4 a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > aiff : < / b > I f a i f f a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > wma : < / b > I f w m a a u d i o i s s u p p o r t e d . < / l i >
* < li > < b > mid : < / b > I f m i d a u d i o i s s u p p o r t e d . < / l i >
2014-02-02 19:31:06 -05:00
* < / u l >
2014-01-03 13:32:13 -05:00
* @ method getCapabilities
* @ return { Object } An object containing the capabilities of the active plugin .
* @ static
* /
s . getCapabilities = function ( ) {
2014-11-18 18:26:26 -05:00
if ( s . activePlugin == null ) { return null ; }
2014-02-02 19:31:06 -05:00
return s . activePlugin . _capabilities ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Get a specific capability of the active plugin . See { { # crossLink "Sound/getCapabilities" } } { { / c r o s s L i n k } } f o r a
* full list of capabilities .
*
* < h4 > Example < / h 4 >
* var maxAudioInstances = createjs . Sound . getCapability ( "tracks" ) ;
*
* @ method getCapability
* @ param { String } key The capability to retrieve
* @ return { Number | Boolean } The value of the capability .
* @ static
* @ see getCapabilities
* /
s . getCapability = function ( key ) {
2014-11-18 18:26:26 -05:00
if ( s . activePlugin == null ) { return null ; }
2014-02-02 19:31:06 -05:00
return s . activePlugin . _capabilities [ key ] ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Process manifest items from < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > . T h i s m e t h o d i s i n t e n d e d
* for usage by a plugin , and not for direct interaction .
* @ method initLoad
* @ param { String | Object } src The src or object to load . This is usually a string path , but can also be an
* HTMLAudioElement or similar audio playback object .
* @ param { String } [ type ] The type of object . Will likely be "sound" or null .
* @ param { String } [ id ] An optional user - specified id that is used to play sounds .
* @ param { Number | String | Boolean | Object } [ data ] Data associated with the item . Sound uses the data parameter as the
* number of channels for an audio instance , however a "channels" property can be appended to the data object if
2014-11-18 18:26:26 -05:00
* this property is used for other information . The audio channels will set a default based on plugin if no value is found .
2014-01-03 13:32:13 -05:00
* @ return { Boolean | Object } An object with the modified values of those that were passed in , or false if the active
* plugin can not play the audio type .
* @ protected
* @ static
* /
2014-11-18 18:26:26 -05:00
s . initLoad = function ( src , type , id , data ) {
return s . _registerSound ( src , id , data ) ;
} ;
/ * *
* Internal method for loading sounds . This should not be called directly .
*
* @ method _registerSound
* @ param { String | Object } src The source to load .
* @ param { String } [ id ] An id specified by the user to play the sound later .
* @ param { Number | Object } [ data ] Data associated with the item . Sound uses the data parameter as the number of
* channels for an audio instance , however a "channels" property can be appended to the data object if it is used
* for other information . The audio channels will set a default based on plugin if no value is found .
* Sound also uses the data property to hold an audioSprite array of objects in the following format { id , startTime , duration } . < br / >
* id used to play the sound later , in the same manner as a sound src with an id . < br / >
* startTime is the initial offset to start playback and loop from , in milliseconds . < br / >
* duration is the amount of time to play the clip for , in milliseconds . < br / >
* This allows Sound to support audio sprites that are played back by id .
* @ return { Object } An object with the modified values that were passed in , which defines the sound .
* Returns false if the source cannot be parsed or no plugins can be initialized .
* Returns true if the source is already loaded .
* @ static
* @ private
* @ since 0.5 . 3
* /
s . _registerSound = function ( src , id , data ) {
if ( ! s . initializeDefaultPlugins ( ) ) { return false ; }
var details = s . _parsePath ( src ) ;
if ( details == null ) { return false ; }
details . type = "sound" ;
details . id = id ;
details . data = data ;
var numChannels = s . activePlugin . defaultNumChannels || null ;
if ( data != null ) {
if ( ! isNaN ( data . channels ) ) {
numChannels = parseInt ( data . channels ) ;
} else if ( ! isNaN ( data ) ) {
numChannels = parseInt ( data ) ;
}
if ( data . audioSprite ) {
var sp ;
for ( var i = data . audioSprite . length ; i -- ; ) {
sp = data . audioSprite [ i ] ;
s . _idHash [ sp . id ] = { src : details . src , startTime : parseInt ( sp . startTime ) , duration : parseInt ( sp . duration ) } ;
}
}
2014-01-03 13:32:13 -05:00
}
2014-11-18 18:26:26 -05:00
if ( id != null ) { s . _idHash [ id ] = { src : details . src } } ;
var loader = s . activePlugin . register ( details . src , numChannels ) ; // Note only HTML audio uses numChannels
SoundChannel . create ( details . src , numChannels ) ;
// return the number of instances to the user. This will also be returned in the load event.
if ( data == null || ! isNaN ( data ) ) {
details . data = numChannels || SoundChannel . maxPerChannel ( ) ;
} else {
details . data . channels = numChannels || SoundChannel . maxPerChannel ( ) ;
}
details . tag = loader . tag ;
if ( loader . completeHandler ) { details . completeHandler = loader . completeHandler ; }
if ( loader . type ) { details . type = loader . type ; }
2014-01-03 13:32:13 -05:00
return details ;
} ;
/ * *
* Register an audio file for loading and future playback in Sound . This is automatically called when using
* < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > . I t i s r e c o m m e n d e d t o r e g i s t e r a l l s o u n d s t h a t
* need to be played back in order to properly prepare and preload them . Sound does internal preloading when required .
*
* < h4 > Example < / h 4 >
* createjs . Sound . alternateExtensions = [ "mp3" ] ;
* createjs . Sound . addEventListener ( "fileload" , handleLoad ) ; // add an event listener for when load is completed
* createjs . Sound . registerSound ( "myAudioPath/mySound.ogg" , "myID" , 3 ) ;
*
* @ method registerSound
* @ param { String | Object } src The source or an Object with a "src" property
* @ param { String } [ id ] An id specified by the user to play the sound later .
* @ param { Number | Object } [ data ] Data associated with the item . Sound uses the data parameter as the number of
* channels for an audio instance , however a "channels" property can be appended to the data object if it is used
* for other information . The audio channels will set a default based on plugin if no value is found .
2014-11-18 18:26:26 -05:00
* Sound also uses the data property to hold an audioSprite array of objects in the following format { id , startTime , duration } . < br / >
* id used to play the sound later , in the same manner as a sound src with an id . < br / >
* startTime is the initial offset to start playback and loop from , in milliseconds . < br / >
* duration is the amount of time to play the clip for , in milliseconds . < br / >
* This allows Sound to support audio sprites that are played back by id .
2014-01-03 13:32:13 -05:00
* @ param { string } basePath Set a path that will be prepended to src for loading .
* @ return { Object } An object with the modified values that were passed in , which defines the sound .
* Returns false if the source cannot be parsed or no plugins can be initialized .
* Returns true if the source is already loaded .
* @ static
* @ since 0.4 . 0
* /
2014-11-18 18:26:26 -05:00
s . registerSound = function ( src , id , data , basePath ) {
2014-01-03 13:32:13 -05:00
if ( src instanceof Object ) {
2014-11-18 18:26:26 -05:00
basePath = id ;
2014-01-03 13:32:13 -05:00
id = src . id ;
data = src . data ;
src = src . src ;
}
2014-11-18 18:26:26 -05:00
if ( basePath != null ) { src = basePath + src ; }
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
var details = s . _registerSound ( src , id , data ) ;
if ( ! details ) { return false ; }
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
if ( ! s . _preloadHash [ details . src ] ) { s . _preloadHash [ details . src ] = [ ] ; }
s . _preloadHash [ details . src ] . push ( { src : src , id : id , data : details . data } ) ;
if ( s . _preloadHash [ details . src ] . length == 1 ) {
// OJR note this will disallow reloading a sound if loading fails or the source changes
s . activePlugin . preload ( details . src , details . tag ) ;
} else {
if ( s . _preloadHash [ details . src ] [ 0 ] == true ) { return true ; }
2014-01-03 13:32:13 -05:00
}
return details ;
} ;
/ * *
* Register a manifest of audio files for loading and future playback in Sound . It is recommended to register all
* sounds that need to be played back in order to properly prepare and preload them . Sound does internal preloading
* when required .
*
* < h4 > Example < / h 4 >
* var manifest = [
* { src : "asset0.ogg" , id : "example" } ,
* { src : "asset1.ogg" , id : "1" , data : 6 } ,
* { src : "asset2.mp3" , id : "works" }
* ] ;
* createjs . Sound . alternateExtensions = [ "mp3" ] ; // if the passed extension is not supported, try this extension
* createjs . Sound . addEventListener ( "fileload" , handleLoad ) ; // call handleLoad when each sound loads
* createjs . Sound . registerManifest ( manifest , assetPath ) ;
*
* @ method registerManifest
* @ param { Array } manifest An array of objects to load . Objects are expected to be in the format needed for
2014-02-02 19:31:06 -05:00
* { { # crossLink "Sound/registerSound" } } { { / c r o s s L i n k } } : < c o d e > { s r c : s r c U R I , i d : I D , d a t a : D a t a } < / c o d e >
* with "id" and "data" being optional .
* @ param { string } basePath Set a path that will be prepended to each src when loading . When creating , playing , or removing
2014-01-03 13:32:13 -05:00
* audio that was loaded with a basePath by src , the basePath must be included .
* @ return { Object } An array of objects with the modified values that were passed in , which defines each sound .
2014-02-02 19:31:06 -05:00
* Like registerSound , it will return false for any values when the source cannot be parsed or if no plugins can be initialized .
* Also , it will return true for any values when the source is already loaded .
2014-01-03 13:32:13 -05:00
* @ static
* @ since 0.4 . 0
* /
s . registerManifest = function ( manifest , basePath ) {
var returnValues = [ ] ;
for ( var i = 0 , l = manifest . length ; i < l ; i ++ ) {
2014-11-18 18:26:26 -05:00
returnValues [ i ] = createjs . Sound . registerSound ( manifest [ i ] . src , manifest [ i ] . id , manifest [ i ] . data , basePath ) ;
}
2014-01-03 13:32:13 -05:00
return returnValues ;
} ;
/ * *
* Remove a sound that has been registered with { { # crossLink "Sound/registerSound" } } { { / c r o s s L i n k } } o r
* { { # crossLink "Sound/registerManifest" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* < br / > Note this will stop playback on active instances playing this sound before deleting them .
* < br / > Note if you passed in a basePath , you need to pass it or prepend it to the src here .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* createjs . Sound . removeSound ( "myAudioBasePath/mySound.ogg" ) ;
* createjs . Sound . removeSound ( "myID" ) ;
*
* @ method removeSound
* @ param { String | Object } src The src or ID of the audio , or an Object with a "src" property
* @ param { string } basePath Set a path that will be prepended to each src when removing .
* @ return { Boolean } True if sound is successfully removed .
* @ static
* @ since 0.4 . 1
* /
s . removeSound = function ( src , basePath ) {
2014-11-18 18:26:26 -05:00
if ( s . activePlugin == null ) { return false ; }
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
if ( src instanceof Object ) { src = src . src ; }
src = s . _getSrcById ( src ) . src ;
if ( basePath != null ) { src = basePath + src ; }
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
var details = s . _parsePath ( src ) ;
if ( details == null ) { return false ; }
2014-01-03 13:32:13 -05:00
src = details . src ;
2014-02-02 19:31:06 -05:00
for ( var prop in s . _idHash ) {
2014-11-18 18:26:26 -05:00
if ( s . _idHash [ prop ] . src == src ) {
2014-02-02 19:31:06 -05:00
delete ( s . _idHash [ prop ] ) ;
2014-01-03 13:32:13 -05:00
}
}
// clear from SoundChannel, which also stops and deletes all instances
SoundChannel . removeSrc ( src ) ;
2014-02-02 19:31:06 -05:00
delete ( s . _preloadHash [ src ] ) ;
2014-01-03 13:32:13 -05:00
s . activePlugin . removeSound ( src ) ;
return true ;
} ;
/ * *
* Remove a manifest of audio files that have been registered with { { # crossLink "Sound/registerSound" } } { { / c r o s s L i n k } } o r
* { { # crossLink "Sound/registerManifest" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* < br / > Note this will stop playback on active instances playing this audio before deleting them .
* < br / > Note if you passed in a basePath , you need to pass it or prepend it to the src here .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* var manifest = [
* { src : "asset0.ogg" , id : "example" } ,
* { src : "asset1.ogg" , id : "1" , data : 6 } ,
* { src : "asset2.mp3" , id : "works" }
* ] ;
* createjs . Sound . removeManifest ( manifest , assetPath ) ;
*
* @ method removeManifest
* @ param { Array } manifest An array of objects to remove . Objects are expected to be in the format needed for
* { { # crossLink "Sound/removeSound" } } { { / c r o s s L i n k } } : < c o d e > { s r c O r I D : s r c U R I o r I D } < / c o d e >
* @ param { string } basePath Set a path that will be prepended to each src when removing .
* @ return { Object } An array of Boolean values representing if the sounds with the same array index in manifest was
* successfully removed .
* @ static
* @ since 0.4 . 1
* /
s . removeManifest = function ( manifest , basePath ) {
var returnValues = [ ] ;
for ( var i = 0 , l = manifest . length ; i < l ; i ++ ) {
returnValues [ i ] = createjs . Sound . removeSound ( manifest [ i ] . src , basePath ) ;
}
return returnValues ;
} ;
/ * *
* Remove all sounds that have been registered with { { # crossLink "Sound/registerSound" } } { { / c r o s s L i n k } } o r
* { { # crossLink "Sound/registerManifest" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* < br / > Note this will stop playback on all active sound instances before deleting them .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* createjs . Sound . removeAllSounds ( ) ;
*
* @ method removeAllSounds
* @ static
* @ since 0.4 . 1
* /
s . removeAllSounds = function ( ) {
2014-02-02 19:31:06 -05:00
s . _idHash = { } ;
s . _preloadHash = { } ;
2014-01-03 13:32:13 -05:00
SoundChannel . removeAll ( ) ;
2014-11-18 18:26:26 -05:00
if ( s . activePlugin ) { s . activePlugin . removeAllSounds ( ) ; }
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Check if a source has been loaded by internal preloaders . This is necessary to ensure that sounds that are
* not completed preloading will not kick off a new internal preload if they are played .
*
* < h4 > Example < / h 4 >
* var mySound = "assetPath/asset0.ogg" ;
* if ( createjs . Sound . loadComplete ( mySound ) {
* createjs . Sound . play ( mySound ) ;
* }
*
* @ method loadComplete
* @ param { String } src The src or id that is being loaded .
* @ return { Boolean } If the src is already loaded .
* @ since 0.4 . 0
* /
s . loadComplete = function ( src ) {
2014-11-18 18:26:26 -05:00
if ( ! s . isReady ( ) ) { return false ; }
var details = s . _parsePath ( src ) ;
2014-01-03 13:32:13 -05:00
if ( details ) {
2014-11-18 18:26:26 -05:00
src = s . _getSrcById ( details . src ) . src ;
2014-01-03 13:32:13 -05:00
} else {
2014-11-18 18:26:26 -05:00
src = s . _getSrcById ( src ) . src ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
return ( s . _preloadHash [ src ] [ 0 ] == true ) ; // src only loads once, so if it's true for the first it's true for all
2014-01-03 13:32:13 -05:00
} ;
/ * *
2014-11-18 18:26:26 -05:00
* Parse the path of a sound , usually from a manifest item . alternate extensions will be attempted in order if the
* current extension is not supported
2014-02-02 19:31:06 -05:00
* @ method _parsePath
2014-01-03 13:32:13 -05:00
* @ param { String } value The path to an audio source .
* @ return { Object } A formatted object that can be registered with the { { # crossLink "Sound/activePlugin:property" } } { { / c r o s s L i n k } }
* and returned to a preloader like < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > .
* @ protected
* /
2014-11-18 18:26:26 -05:00
s . _parsePath = function ( value ) {
2014-01-03 13:32:13 -05:00
if ( typeof ( value ) != "string" ) { value = value . toString ( ) ; }
var match = value . match ( s . FILE _PATTERN ) ;
2014-11-18 18:26:26 -05:00
if ( match == null ) { return false ; }
2014-01-03 13:32:13 -05:00
var name = match [ 4 ] ;
var ext = match [ 5 ] ;
var c = s . getCapabilities ( ) ;
var i = 0 ;
while ( ! c [ ext ] ) {
ext = s . alternateExtensions [ i ++ ] ;
if ( i > s . alternateExtensions . length ) { return null ; } // no extensions are supported
}
value = value . replace ( "." + match [ 5 ] , "." + ext ) ;
2014-11-18 18:26:26 -05:00
var ret = { name : name , src : value , extension : ext } ;
2014-01-03 13:32:13 -05:00
return ret ;
} ;
/ * - - - - - - - - - - - - - - -
Static API .
-- -- -- -- -- -- -- - * /
/ * *
* Play a sound and get a { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } t o c o n t r o l . I f t h e s o u n d f a i l s t o p l a y , a
* SoundInstance will still be returned , and have a playState of { { # crossLink "Sound/PLAY_FAILED:property" } } { { / c r o s s L i n k } } .
* Note that even on sounds with failed playback , you may still be able to call SoundInstance { { # crossLink "SoundInstance/play" } } { { / c r o s s L i n k } } ,
* since the failure could be due to lack of available channels . If the src does not have a supported extension or
2014-02-02 19:31:06 -05:00
* if there is no available plugin , a default SoundInstance will be returned which will not play any audio , but will not generate errors .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* createjs . Sound . addEventListener ( "fileload" , handleLoad ) ;
* createjs . Sound . registerSound ( "myAudioPath/mySound.mp3" , "myID" , 3 ) ;
* function handleLoad ( event ) {
* createjs . Sound . play ( "myID" ) ;
2014-02-02 19:31:06 -05:00
* // we can pass in options we want to set inside of an object, and store off SoundInstance for controlling
* var myInstance = createjs . Sound . play ( "myID" , { interrupt : createjs . Sound . INTERRUPT _ANY , loop : - 1 } ) ;
* // alternately, we can pass full source path and specify each argument individually
2014-01-03 13:32:13 -05:00
* var myInstance = createjs . Sound . play ( "myAudioPath/mySound.mp3" , createjs . Sound . INTERRUPT _ANY , 0 , 0 , - 1 , 1 , 0 ) ;
* }
*
2014-11-18 18:26:26 -05:00
* NOTE to create an audio sprite that has not already been registered , both startTime and duration need to be set .
* This is only when creating a new audio sprite , not when playing using the id of an already registered audio sprite .
*
2014-01-03 13:32:13 -05:00
* @ method play
* @ param { String } src The src or ID of the audio .
* @ param { String | Object } [ interrupt = "none" | options ] How to interrupt any currently playing instances of audio with the same source ,
* if the maximum number of instances of the sound are already playing . Values are defined as < code > INTERRUPT _TYPE < / c o d e >
* constants on the Sound class , with the default defined by { { # crossLink "Sound/defaultInterruptBehavior:property" } } { { / c r o s s L i n k } } .
* < br / > < strong > OR < /strong><br / >
* This parameter can be an object that contains any or all optional properties by name , including : interrupt ,
2014-11-18 18:26:26 -05:00
* delay , offset , loop , volume , pan , startTime , and duration ( see the above code sample ) .
2014-01-03 13:32:13 -05:00
* @ param { Number } [ delay = 0 ] The amount of time to delay the start of audio playback , in milliseconds .
* @ param { Number } [ offset = 0 ] The offset from the start of the audio to begin playback , in milliseconds .
* @ param { Number } [ loop = 0 ] How many times the audio loops when it reaches the end of playback . The default is 0 ( no
* loops ) , and - 1 can be used for infinite playback .
* @ param { Number } [ volume = 1 ] The volume of the sound , between 0 and 1. Note that the master volume is applied
* against the individual volume .
* @ param { Number } [ pan = 0 ] The left - right pan of the sound ( if supported ) , between - 1 ( left ) and 1 ( right ) .
2014-11-18 18:26:26 -05:00
* @ param { Number } [ startTime = null ] To create an audio sprite ( with duration ) , the initial offset to start playback and loop from , in milliseconds .
* @ param { Number } [ duration = null ] To create an audio sprite ( with startTime ) , the amount of time to play the clip for , in milliseconds .
2014-01-03 13:32:13 -05:00
* @ return { SoundInstance } A { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } t h a t c a n b e c o n t r o l l e d a f t e r i t i s c r e a t e d .
* @ static
* /
2014-11-18 18:26:26 -05:00
s . play = function ( src , interrupt , delay , offset , loop , volume , pan , startTime , duration ) {
if ( interrupt instanceof Object ) {
delay = interrupt . delay ;
offset = interrupt . offset ;
loop = interrupt . loop ;
volume = interrupt . volume ;
pan = interrupt . pan ;
startTime = interrupt . startTime ;
duration = interrupt . duration ;
interrupt = interrupt . interrupt ;
2014-01-03 13:32:13 -05:00
}
2014-11-18 18:26:26 -05:00
var instance = s . createInstance ( src , startTime , duration ) ;
var ok = s . _playInstance ( instance , interrupt , delay , offset , loop , volume , pan ) ;
if ( ! ok ) { instance . playFailed ( ) ; }
2014-01-03 13:32:13 -05:00
return instance ;
} ;
/ * *
* Creates a { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } u s i n g t h e p a s s e d i n s r c . I f t h e s r c d o e s n o t h a v e a
2014-02-02 19:31:06 -05:00
* supported extension or if there is no available plugin , a default SoundInstance will be returned that can be
* called safely but does nothing .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* var myInstance = null ;
* createjs . Sound . addEventListener ( "fileload" , handleLoad ) ;
* createjs . Sound . registerSound ( "myAudioPath/mySound.mp3" , "myID" , 3 ) ;
* function handleLoad ( event ) {
* myInstance = createjs . Sound . createInstance ( "myID" ) ;
* // alternately we could call the following
* myInstance = createjs . Sound . createInstance ( "myAudioPath/mySound.mp3" ) ;
* }
*
2014-11-18 18:26:26 -05:00
* NOTE to create an audio sprite that has not already been registered , both startTime and duration need to be set .
* This is only when creating a new audio sprite , not when playing using the id of an already registered audio sprite .
*
2014-01-03 13:32:13 -05:00
* @ method createInstance
* @ param { String } src The src or ID of the audio .
2014-11-18 18:26:26 -05:00
* @ param { Number } [ startTime = null ] To create an audio sprite ( with duration ) , the initial offset to start playback and loop from , in milliseconds .
* @ param { Number } [ duration = null ] To create an audio sprite ( with startTime ) , the amount of time to play the clip for , in milliseconds .
2014-01-03 13:32:13 -05:00
* @ return { SoundInstance } A { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } t h a t c a n b e c o n t r o l l e d a f t e r i t i s c r e a t e d .
* Unsupported extensions will return the default SoundInstance .
* @ since 0.4 . 0
* /
2014-11-18 18:26:26 -05:00
s . createInstance = function ( src , startTime , duration ) {
if ( ! s . initializeDefaultPlugins ( ) ) { return s . _defaultSoundInstance ; }
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
src = s . _getSrcById ( src ) ;
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
var details = s . _parsePath ( src . src ) ;
2014-01-03 13:32:13 -05:00
var instance = null ;
if ( details != null && details . src != null ) {
SoundChannel . create ( details . src ) ;
2014-11-18 18:26:26 -05:00
if ( startTime == null ) { startTime = src . startTime ; }
instance = s . activePlugin . create ( details . src , startTime , duration || src . duration ) ;
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
instance = Sound . _defaultSoundInstance ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
instance . uniqueId = s . _lastID ++ ;
2014-01-03 13:32:13 -05:00
return instance ;
} ;
/ * *
* Set the master volume of Sound . The master volume is multiplied against each sound ' s individual volume . For
* example , if master volume is 0.5 and a sound ' s volume is 0.5 , the resulting volume is 0.25 . To set individual
* sound volume , use SoundInstance { { # crossLink "SoundInstance/setVolume" } } { { / c r o s s L i n k } } i n s t e a d .
*
* < h4 > Example < / h 4 >
* createjs . Sound . setVolume ( 0.5 ) ;
*
* @ method setVolume
* @ param { Number } value The master volume value . The acceptable range is 0 - 1.
* @ static
* /
s . setVolume = function ( value ) {
2014-11-18 18:26:26 -05:00
if ( Number ( value ) == null ) { return false ; }
2014-01-03 13:32:13 -05:00
value = Math . max ( 0 , Math . min ( 1 , value ) ) ;
2014-02-02 19:31:06 -05:00
s . _masterVolume = value ;
2014-01-03 13:32:13 -05:00
if ( ! this . activePlugin || ! this . activePlugin . setVolume || ! this . activePlugin . setVolume ( value ) ) {
2014-11-18 18:26:26 -05:00
var instances = this . _instances ;
2014-01-03 13:32:13 -05:00
for ( var i = 0 , l = instances . length ; i < l ; i ++ ) {
instances [ i ] . setMasterVolume ( value ) ;
}
}
} ;
/ * *
* Get the master volume of Sound . The master volume is multiplied against each sound ' s individual volume .
2014-02-02 19:31:06 -05:00
* To get individual sound volume , use SoundInstance { { # crossLink "SoundInstance/volume:property" } } { { / c r o s s L i n k } } i n s t e a d .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* var masterVolume = createjs . Sound . getVolume ( ) ;
*
* @ method getVolume
* @ return { Number } The master volume , in a range of 0 - 1.
* @ static
* /
s . getVolume = function ( ) {
2014-02-02 19:31:06 -05:00
return s . _masterVolume ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Mute / Unmute all audio . Note that muted audio still plays at 0 volume . This global mute value is maintained
* separately and when set will override , but not change the mute property of individual instances . To mute an individual
* instance , use SoundInstance { { # crossLink "SoundInstance/setMute" } } { { / c r o s s L i n k } } i n s t e a d .
*
* < h4 > Example < / h 4 >
* createjs . Sound . setMute ( true ) ;
*
* @ method setMute
* @ param { Boolean } value Whether the audio should be muted or not .
* @ return { Boolean } If the mute was set .
* @ static
* @ since 0.4 . 0
* /
s . setMute = function ( value ) {
2014-11-18 18:26:26 -05:00
if ( value == null ) { return false ; }
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . _masterMute = value ;
2014-01-03 13:32:13 -05:00
if ( ! this . activePlugin || ! this . activePlugin . setMute || ! this . activePlugin . setMute ( value ) ) {
2014-02-02 19:31:06 -05:00
var instances = this . _instances ;
2014-01-03 13:32:13 -05:00
for ( var i = 0 , l = instances . length ; i < l ; i ++ ) {
instances [ i ] . setMasterMute ( value ) ;
}
}
return true ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
* Returns the global mute value . To get the mute value of an individual instance , use SoundInstance
* { { # crossLink "SoundInstance/getMute" } } { { / c r o s s L i n k } } i n s t e a d .
*
* < h4 > Example < / h 4 >
2014-02-02 19:31:06 -05:00
* var muted = createjs . Sound . getMute ( ) ;
2014-01-03 13:32:13 -05:00
*
* @ method getMute
* @ return { Boolean } The mute value of Sound .
* @ static
* @ since 0.4 . 0
* /
s . getMute = function ( ) {
2014-02-02 19:31:06 -05:00
return this . _masterMute ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Stop all audio ( global stop ) . Stopped audio is reset , and not paused . To play audio that has been stopped ,
2014-02-02 19:31:06 -05:00
* call SoundInstance { { # crossLink "SoundInstance/play" } } { { / c r o s s L i n k } } .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
* createjs . Sound . stop ( ) ;
*
* @ method stop
* @ static
* /
s . stop = function ( ) {
2014-02-02 19:31:06 -05:00
var instances = this . _instances ;
2014-01-03 13:32:13 -05:00
for ( var i = instances . length ; i -- ; ) {
2014-02-02 19:31:06 -05:00
instances [ i ] . stop ( ) ; // NOTE stop removes instance from this._instances
2014-01-03 13:32:13 -05:00
}
} ;
/ * - - - - - - - - - - - - - - -
Internal methods
-- -- -- -- -- -- -- - * /
/ * *
* Play an instance . This is called by the static API , as well as from plugins . This allows the core class to
* control delays .
2014-02-02 19:31:06 -05:00
* @ method _playInstance
2014-01-03 13:32:13 -05:00
* @ param { SoundInstance } instance The { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } t o s t a r t p l a y i n g .
* @ param { String | Object } [ interrupt = "none" | options ] How to interrupt any currently playing instances of audio with the same source ,
* if the maximum number of instances of the sound are already playing . Values are defined as < code > INTERRUPT _TYPE < / c o d e >
* constants on the Sound class , with the default defined by { { # crossLink "Sound/defaultInterruptBehavior" } } { { / c r o s s L i n k } } .
* < br / > < strong > OR < /strong><br / >
* This parameter can be an object that contains any or all optional properties by name , including : interrupt ,
* delay , offset , loop , volume , and pan ( see the above code sample ) .
* @ param { Number } [ delay = 0 ] Time in milliseconds before playback begins .
* @ param { Number } [ offset = instance . offset ] Time into the sound to begin playback in milliseconds . Defaults to the
* current value on the instance .
* @ param { Number } [ loop = 0 ] The number of times to loop the audio . Use 0 for no loops , and - 1 for an infinite loop .
* @ param { Number } [ volume ] The volume of the sound between 0 and 1. Defaults to current instance value .
* @ param { Number } [ pan ] The pan of the sound between - 1 and 1. Defaults to current instance value .
* @ return { Boolean } If the sound can start playing . Sounds that fail immediately will return false . Sounds that
* have a delay will return true , but may still fail to play .
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _playInstance = function ( instance , interrupt , delay , offset , loop , volume , pan ) {
2014-01-03 13:32:13 -05:00
if ( interrupt instanceof Object ) {
delay = interrupt . delay ;
offset = interrupt . offset ;
loop = interrupt . loop ;
volume = interrupt . volume ;
pan = interrupt . pan ;
2014-02-02 19:31:06 -05:00
interrupt = interrupt . interrupt ;
2014-01-03 13:32:13 -05:00
}
interrupt = interrupt || s . defaultInterruptBehavior ;
if ( delay == null ) { delay = 0 ; }
if ( offset == null ) { offset = instance . getPosition ( ) ; }
2014-11-18 18:26:26 -05:00
if ( loop == null ) { loop = instance . loop ; }
2014-01-03 13:32:13 -05:00
if ( volume == null ) { volume = instance . volume ; }
if ( pan == null ) { pan = instance . pan ; }
if ( delay == 0 ) {
2014-02-02 19:31:06 -05:00
var ok = s . _beginPlaying ( instance , interrupt , offset , loop , volume , pan ) ;
2014-11-18 18:26:26 -05:00
if ( ! ok ) { return false ; }
2014-01-03 13:32:13 -05:00
} else {
//Note that we can't pass arguments to proxy OR setTimeout (IE only), so just wrap the function call.
// OJR WebAudio may want to handle this differently, so it might make sense to move this functionality into the plugins in the future
var delayTimeoutId = setTimeout ( function ( ) {
2014-02-02 19:31:06 -05:00
s . _beginPlaying ( instance , interrupt , offset , loop , volume , pan ) ;
2014-01-03 13:32:13 -05:00
} , delay ) ;
2014-02-02 19:31:06 -05:00
instance . _delayTimeoutId = delayTimeoutId ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
this . _instances . push ( instance ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Begin playback . This is called immediately or after delay by { { # crossLink "Sound/playInstance" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* @ method _beginPlaying
2014-01-03 13:32:13 -05:00
* @ param { SoundInstance } instance A { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } t o b e g i n p l a y b a c k .
* @ param { String } [ interrupt = none ] How this sound interrupts other instances with the same source . Defaults to
* { { # crossLink "Sound/INTERRUPT_NONE:property" } } { { / c r o s s L i n k } } . I n t e r r u p t s a r e d e f i n e d a s < c o d e > I N T E R R U P T _ T Y P E < / c o d e >
* constants on Sound .
* @ param { Number } [ offset ] Time in milliseconds into the sound to begin playback . Defaults to the current value on
* the instance .
* @ param { Number } [ loop = 0 ] The number of times to loop the audio . Use 0 for no loops , and - 1 for an infinite loop .
* @ param { Number } [ volume ] The volume of the sound between 0 and 1. Defaults to the current value on the instance .
* @ param { Number } [ pan = instance . pan ] The pan of the sound between - 1 and 1. Defaults to current instance value .
* @ return { Boolean } If the sound can start playing . If there are no available channels , or the instance fails to
* start , this will return false .
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _beginPlaying = function ( instance , interrupt , offset , loop , volume , pan ) {
2014-01-03 13:32:13 -05:00
if ( ! SoundChannel . add ( instance , interrupt ) ) {
return false ;
}
2014-02-02 19:31:06 -05:00
var result = instance . _beginPlaying ( offset , loop , volume , pan ) ;
2014-01-03 13:32:13 -05:00
if ( ! result ) {
2014-02-02 19:31:06 -05:00
var index = createjs . indexOf ( this . _instances , instance ) ;
2014-11-18 18:26:26 -05:00
if ( index > - 1 ) { this . _instances . splice ( index , 1 ) ; }
2014-01-03 13:32:13 -05:00
return false ;
}
return true ;
} ;
/ * *
* Get the source of a sound via the ID passed in with a register call . If no ID is found the value is returned
* instead .
2014-02-02 19:31:06 -05:00
* @ method _getSrcById
2014-01-03 13:32:13 -05:00
* @ param { String } value The ID the sound was registered with .
2014-11-18 18:26:26 -05:00
* @ return { String } The source of the sound if it has been registered with this ID or the value that was passed in .
2014-01-03 13:32:13 -05:00
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _getSrcById = function ( value ) {
2014-11-18 18:26:26 -05:00
return s . _idHash [ value ] || { src : value } ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* A sound has completed playback , been interrupted , failed , or been stopped . This method removes the instance from
* Sound management . It will be added again , if the sound re - plays . Note that this method is called from the
* instances themselves .
2014-02-02 19:31:06 -05:00
* @ method _playFinished
2014-01-03 13:32:13 -05:00
* @ param { SoundInstance } instance The instance that finished playback .
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _playFinished = function ( instance ) {
2014-01-03 13:32:13 -05:00
SoundChannel . remove ( instance ) ;
2014-02-02 19:31:06 -05:00
var index = createjs . indexOf ( this . _instances , instance ) ;
2014-11-18 18:26:26 -05:00
if ( index > - 1 ) { this . _instances . splice ( index , 1 ) ; } // OJR this will always be > -1, there is no way for an instance to exist without being added to this._instances
2014-01-03 13:32:13 -05:00
} ;
createjs . Sound = Sound ;
/ * *
* An internal class that manages the number of active { { # crossLink "SoundInstance" } } { { / c r o s s L i n k } } i n s t a n c e s f o r
* each sound type . This method is only used internally by the { { # crossLink "Sound" } } { { / c r o s s L i n k } } c l a s s .
*
* The number of sounds is artificially limited by Sound in order to prevent over - saturation of a
* single sound , as well as to stay within hardware limitations , although the latter may disappear with better
* browser support .
*
* When a sound is played , this class ensures that there is an available instance , or interrupts an appropriate
* sound that is already playing .
* # class SoundChannel
* @ param { String } src The source of the instances
* @ param { Number } [ max = 1 ] The number of instances allowed
* @ constructor
* @ protected
* /
function SoundChannel ( src , max ) {
this . init ( src , max ) ;
}
/ * - - - - - - - - - - - -
Static API
-- -- -- -- -- -- * /
/ * *
* A hash of channel instances indexed by source .
* # property channels
* @ type { Object }
* @ static
* /
SoundChannel . channels = { } ;
/ * *
* Create a sound channel . Note that if the sound channel already exists , this will fail .
* # method create
* @ param { String } src The source for the channel
* @ param { Number } max The maximum amount this channel holds . The default is { { # crossLink "SoundChannel.maxDefault" } } { { / c r o s s L i n k } } .
* @ return { Boolean } If the channels were created .
* @ static
* /
SoundChannel . create = function ( src , max ) {
var channel = SoundChannel . get ( src ) ;
if ( channel == null ) {
SoundChannel . channels [ src ] = new SoundChannel ( src , max ) ;
return true ;
}
return false ;
} ;
/ * *
* Delete a sound channel , stop and delete all related instances . Note that if the sound channel does not exist , this will fail .
* # method remove
* @ param { String } src The source for the channel
* @ return { Boolean } If the channels were deleted .
* @ static
* /
SoundChannel . removeSrc = function ( src ) {
var channel = SoundChannel . get ( src ) ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return false ; }
channel . _removeAll ( ) ; // this stops and removes all active instances
2014-01-03 13:32:13 -05:00
delete ( SoundChannel . channels [ src ] ) ;
return true ;
} ;
/ * *
* Delete all sound channels , stop and delete all related instances .
* # method removeAll
* @ static
* /
SoundChannel . removeAll = function ( ) {
for ( var channel in SoundChannel . channels ) {
2014-11-18 18:26:26 -05:00
SoundChannel . channels [ channel ] . _removeAll ( ) ; // this stops and removes all active instances
2014-01-03 13:32:13 -05:00
}
SoundChannel . channels = { } ;
} ;
/ * *
* Add an instance to a sound channel .
* # method add
* @ param { SoundInstance } instance The instance to add to the channel
* @ param { String } interrupt The interrupt value to use . Please see the { { # crossLink "Sound/play" } } { { / c r o s s L i n k } }
* for details on interrupt modes .
* @ return { Boolean } The success of the method call . If the channel is full , it will return false .
* @ static
* /
SoundChannel . add = function ( instance , interrupt ) {
var channel = SoundChannel . get ( instance . src ) ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return false ; }
return channel . _add ( instance , interrupt ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Remove an instance from the channel .
* # method remove
* @ param { SoundInstance } instance The instance to remove from the channel
* @ return The success of the method call . If there is no channel , it will return false .
* @ static
* /
SoundChannel . remove = function ( instance ) {
var channel = SoundChannel . get ( instance . src ) ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return false ; }
channel . _remove ( instance ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Get the maximum number of sounds you can have in a channel .
* # method maxPerChannel
* @ return { Number } The maximum number of sounds you can have in a channel .
* /
SoundChannel . maxPerChannel = function ( ) {
return p . maxDefault ;
} ;
/ * *
* Get a channel instance by its src .
* # method get
* @ param { String } src The src to use to look up the channel
* @ static
* /
SoundChannel . get = function ( src ) {
return SoundChannel . channels [ src ] ;
} ;
var p = SoundChannel . prototype ;
2014-11-18 18:26:26 -05:00
p . constructor = SoundChannel ;
2014-01-03 13:32:13 -05:00
/ * *
* The source of the channel .
* # property src
* @ type { String }
* /
p . src = null ;
/ * *
* The maximum number of instances in this channel . - 1 indicates no limit
* # property max
* @ type { Number }
* /
p . max = null ;
/ * *
* The default value to set for max , if it isn ' t passed in . Also used if - 1 is passed .
* # property maxDefault
* @ type { Number }
* @ default 100
* @ since 0.4 . 0
* /
p . maxDefault = 100 ;
/ * *
* The current number of active instances .
* # property length
* @ type { Number }
* /
p . length = 0 ;
/ * *
* Initialize the channel .
* # method init
* @ param { String } src The source of the channel
* @ param { Number } max The maximum number of instances in the channel
* @ protected
* /
p . init = function ( src , max ) {
this . src = src ;
this . max = max || this . maxDefault ;
2014-11-18 18:26:26 -05:00
if ( this . max == - 1 ) { this . max = this . maxDefault ; }
2014-02-02 19:31:06 -05:00
this . _instances = [ ] ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Get an instance by index .
* # method get
* @ param { Number } index The index to return .
* @ return { SoundInstance } The SoundInstance at a specific instance .
* /
2014-11-18 18:26:26 -05:00
p . _get = function ( index ) {
2014-02-02 19:31:06 -05:00
return this . _instances [ index ] ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Add a new instance to the channel .
* # method add
* @ param { SoundInstance } instance The instance to add .
* @ return { Boolean } The success of the method call . If the channel is full , it will return false .
* /
2014-11-18 18:26:26 -05:00
p . _add = function ( instance , interrupt ) {
if ( ! this . _getSlot ( interrupt , instance ) ) { return false ; }
2014-02-02 19:31:06 -05:00
this . _instances . push ( instance ) ;
2014-01-03 13:32:13 -05:00
this . length ++ ;
return true ;
} ;
/ * *
* Remove an instance from the channel , either when it has finished playing , or it has been interrupted .
* # method remove
* @ param { SoundInstance } instance The instance to remove
* @ return { Boolean } The success of the remove call . If the instance is not found in this channel , it will
* return false .
* /
2014-11-18 18:26:26 -05:00
p . _remove = function ( instance ) {
2014-02-02 19:31:06 -05:00
var index = createjs . indexOf ( this . _instances , instance ) ;
2014-11-18 18:26:26 -05:00
if ( index == - 1 ) { return false ; }
2014-02-02 19:31:06 -05:00
this . _instances . splice ( index , 1 ) ;
2014-01-03 13:32:13 -05:00
this . length -- ;
return true ;
} ;
/ * *
* Stop playback and remove all instances from the channel . Usually in response to a delete call .
* # method removeAll
* /
2014-11-18 18:26:26 -05:00
p . _removeAll = function ( ) {
// Note that stop() removes the item from the list
2014-01-03 13:32:13 -05:00
for ( var i = this . length - 1 ; i >= 0 ; i -- ) {
2014-02-02 19:31:06 -05:00
this . _instances [ i ] . stop ( ) ;
2014-01-03 13:32:13 -05:00
}
} ;
/ * *
* Get an available slot depending on interrupt value and if slots are available .
* # method getSlot
* @ param { String } interrupt The interrupt value to use .
* @ param { SoundInstance } instance The sound instance that will go in the channel if successful .
* @ return { Boolean } Determines if there is an available slot . Depending on the interrupt mode , if there are no slots ,
* an existing SoundInstance may be interrupted . If there are no slots , this method returns false .
* /
2014-11-18 18:26:26 -05:00
p . _getSlot = function ( interrupt , instance ) {
2014-01-03 13:32:13 -05:00
var target , replacement ;
2014-11-18 18:26:26 -05:00
if ( interrupt != Sound . INTERRUPT _NONE ) {
// First replacement candidate
replacement = this . _get ( 0 ) ;
if ( replacement == null ) {
return true ;
}
}
2014-01-03 13:32:13 -05:00
for ( var i = 0 , l = this . max ; i < l ; i ++ ) {
2014-11-18 18:26:26 -05:00
target = this . _get ( i ) ;
2014-01-03 13:32:13 -05:00
// Available Space
if ( target == null ) {
return true ;
}
// Audio is complete or not playing
if ( target . playState == Sound . PLAY _FINISHED ||
2014-11-18 18:26:26 -05:00
target . playState == Sound . PLAY _INTERRUPTED ||
target . playState == Sound . PLAY _FAILED ) {
2014-01-03 13:32:13 -05:00
replacement = target ;
2014-11-18 18:26:26 -05:00
break ;
}
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
if ( interrupt == Sound . INTERRUPT _NONE ) {
continue ;
}
// Audio is a better candidate than the current target, according to playhead
if ( ( interrupt == Sound . INTERRUPT _EARLY && target . getPosition ( ) < replacement . getPosition ( ) ) ||
( interrupt == Sound . INTERRUPT _LATE && target . getPosition ( ) > replacement . getPosition ( ) ) ) {
replacement = target ;
2014-01-03 13:32:13 -05:00
}
}
if ( replacement != null ) {
2014-02-02 19:31:06 -05:00
replacement . _interrupt ( ) ;
2014-11-18 18:26:26 -05:00
this . _remove ( replacement ) ;
2014-01-03 13:32:13 -05:00
return true ;
}
return false ;
} ;
p . toString = function ( ) {
return "[Sound SoundChannel]" ;
} ;
// do not add SoundChannel to namespace
// This is a dummy sound instance, which allows Sound to return something so developers don't need to check nulls.
function SoundInstance ( ) {
this . isDefault = true ;
2014-11-18 18:26:26 -05:00
this . addEventListener = this . on = this . off = this . removeEventListener = this . removeAllEventListeners = this . dispatchEvent = this . hasEventListener = this . _listeners = this . _interrupt = this . _playFailed = this . pause = this . resume = this . play = this . _beginPlaying = this . _cleanUp = this . stop = this . setMasterVolume = this . setVolume = this . mute = this . setMute = this . getMute = this . setPan = this . getPosition = this . setPosition = this . playFailed = function ( ) {
2014-01-03 13:32:13 -05:00
return false ;
} ;
this . getVolume = this . getPan = this . getDuration = function ( ) {
return 0 ;
}
this . playState = Sound . PLAY _FAILED ;
this . toString = function ( ) {
return "[Sound Default Sound Instance]" ;
}
}
2014-02-02 19:31:06 -05:00
Sound . _defaultSoundInstance = new SoundInstance ( ) ;
2014-01-03 13:32:13 -05:00
/ * *
* An additional module to determine the current browser , version , operating system , and other environment
* variables . It is not publically documented .
* # class BrowserDetect
* @ param { Boolean } isFirefox True if our browser is Firefox .
* @ param { Boolean } isOpera True if our browser is opera .
* @ param { Boolean } isChrome True if our browser is Chrome . Note that Chrome for Android returns true , but is a
* completely different browser with different abilities .
* @ param { Boolean } isIOS True if our browser is safari for iOS devices ( iPad , iPhone , and iPad ) .
* @ param { Boolean } isAndroid True if our browser is Android .
* @ param { Boolean } isBlackberry True if our browser is Blackberry .
* @ constructor
* @ static
* /
function BrowserDetect ( ) {
}
BrowserDetect . init = function ( ) {
var agent = window . navigator . userAgent ;
2014-11-18 18:26:26 -05:00
BrowserDetect . isWindowPhone = ( agent . indexOf ( "IEMobile" ) > - 1 ) || ( agent . indexOf ( "Windows Phone" ) > - 1 ) ;
2014-01-03 13:32:13 -05:00
BrowserDetect . isFirefox = ( agent . indexOf ( "Firefox" ) > - 1 ) ;
BrowserDetect . isOpera = ( window . opera != null ) ;
BrowserDetect . isChrome = ( agent . indexOf ( "Chrome" ) > - 1 ) ; // NOTE that Chrome on Android returns true but is a completely different browser with different abilities
2014-11-18 18:26:26 -05:00
BrowserDetect . isIOS = ( agent . indexOf ( "iPod" ) > - 1 || agent . indexOf ( "iPhone" ) > - 1 || agent . indexOf ( "iPad" ) > - 1 ) && ! BrowserDetect . isWindowPhone ;
2014-01-03 13:32:13 -05:00
BrowserDetect . isAndroid = ( agent . indexOf ( "Android" ) > - 1 ) ;
BrowserDetect . isBlackberry = ( agent . indexOf ( "Blackberry" ) > - 1 ) ;
} ;
BrowserDetect . init ( ) ;
createjs . Sound . BrowserDetect = BrowserDetect ;
} ( ) ) ;
/ *
* WebAudioPlugin
* 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" ;
/ * *
2014-02-02 19:31:06 -05:00
* Play sounds using Web Audio in the browser . The WebAudioPlugin is currently the default plugin , and will be used
* anywhere that it is supported . To change plugin priority , check out the Sound API
* { { # crossLink "Sound/registerPlugins" } } { { / c r o s s L i n k } } m e t h o d .
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
* < h4 > Known Browser and OS issues for Web Audio < / h 4 >
2014-01-03 13:32:13 -05:00
* < b > Firefox 25 < / b >
* < ul > < li > mp3 audio files do not load properly on all windows machines , reported
* < a href = "https://bugzilla.mozilla.org/show_bug.cgi?id=929969" target = "_blank" > here < / a > . < / b r >
2014-02-02 19:31:06 -05:00
* For this reason it is recommended to pass another FF supported type ( ie ogg ) first until this bug is resolved , if possible . < / l i > < / u l >
2014-01-03 13:32:13 -05:00
* < br / >
* < b > Webkit ( Chrome and Safari ) < / b >
* < ul > < li > AudioNode . disconnect does not always seem to work . This can cause the file size to grow over time if you
* are playing a lot of audio files . < / l i > < / u l >
* < br / >
* < b > iOS 6 limitations < / b >
* < ul > < li > Sound is initially muted and will only unmute through play being called inside a user initiated event ( touch / click ) . < / l i >
2014-02-02 19:31:06 -05:00
* < li > A bug exists that will distort uncached audio when a video element is present in the DOM . You can avoid this bug
* by ensuring the audio and video audio share the same sampleRate . < / l i >
2014-01-03 13:32:13 -05:00
* < / u l >
* @ class WebAudioPlugin
* @ constructor
* @ since 0.4 . 0
* /
function WebAudioPlugin ( ) {
2014-02-02 19:31:06 -05:00
this . _init ( ) ;
2014-01-03 13:32:13 -05:00
}
var s = WebAudioPlugin ;
/ * *
2014-02-02 19:31:06 -05:00
* The capabilities of the plugin . This is generated via the { { # crossLink "WebAudioPlugin/_generateCapabilities:method" } } { { / c r o s s L i n k } }
2014-01-03 13:32:13 -05:00
* method and is used internally .
2014-02-02 19:31:06 -05:00
* @ property _capabilities
2014-01-03 13:32:13 -05:00
* @ type { Object }
* @ default null
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _capabilities = null ;
2014-01-03 13:32:13 -05:00
/ * *
* Determine if the plugin can be used in the current browser / OS .
* @ method isSupported
* @ return { Boolean } If the plugin can be initialized .
* @ static
* /
s . isSupported = function ( ) {
// check if this is some kind of mobile device, Web Audio works with local protocol under PhoneGap and it is unlikely someone is trying to run a local file
var isMobilePhoneGap = createjs . Sound . BrowserDetect . isIOS || createjs . Sound . BrowserDetect . isAndroid || createjs . Sound . BrowserDetect . isBlackberry ;
2014-02-02 19:31:06 -05:00
// OJR isMobile may be redundant with _isFileXHRSupported available. Consider removing.
if ( location . protocol == "file:" && ! isMobilePhoneGap && ! this . _isFileXHRSupported ( ) ) { return false ; } // Web Audio requires XHR, which is not usually available locally
s . _generateCapabilities ( ) ;
2014-11-18 18:26:26 -05:00
if ( s . context == null ) { return false ; }
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Determine if XHR is supported , which is necessary for web audio .
2014-02-02 19:31:06 -05:00
* @ method _isFileXHRSupported
2014-01-03 13:32:13 -05:00
* @ return { Boolean } If XHR is supported .
* @ since 0.4 . 2
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _isFileXHRSupported = function ( ) {
// it's much easier to detect when something goes wrong, so let's start optimistically
2014-01-03 13:32:13 -05:00
var supported = true ;
var xhr = new XMLHttpRequest ( ) ;
try {
2014-11-18 18:26:26 -05:00
xhr . open ( "GET" , "WebAudioPluginTest.fail" , false ) ; // loading non-existant file triggers 404 only if it could load (synchronous call)
2014-01-03 13:32:13 -05:00
} catch ( error ) {
// catch errors in cases where the onerror is passed by
supported = false ;
return supported ;
}
xhr . onerror = function ( ) { supported = false ; } ; // cause irrelevant
// with security turned off, we can get empty success results, which is actually a failed read (status code 0?)
xhr . onload = function ( ) { supported = this . status == 404 || ( this . status == 200 || ( this . status == 0 && this . response != "" ) ) ; } ;
try {
xhr . send ( ) ;
} catch ( error ) {
// catch errors in cases where the onerror is passed by
supported = false ;
}
return supported ;
} ;
/ * *
* Determine the capabilities of the plugin . Used internally . Please see the Sound API { { # crossLink "Sound/getCapabilities" } } { { / c r o s s L i n k } }
* method for an overview of plugin capabilities .
2014-02-02 19:31:06 -05:00
* @ method _generateCapabilities
2014-01-03 13:32:13 -05:00
* @ static
* @ protected
* /
2014-02-02 19:31:06 -05:00
s . _generateCapabilities = function ( ) {
2014-11-18 18:26:26 -05:00
if ( s . _capabilities != null ) { return ; }
// Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section
2014-01-03 13:32:13 -05:00
var t = document . createElement ( "audio" ) ;
2014-11-18 18:26:26 -05:00
if ( t . canPlayType == null ) { return null ; }
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
if ( window . AudioContext ) {
2014-01-03 13:32:13 -05:00
s . context = new AudioContext ( ) ;
2014-11-18 18:26:26 -05:00
} else if ( window . webkitAudioContext ) {
s . context = new webkitAudioContext ( ) ;
2014-01-03 13:32:13 -05:00
} else {
return null ;
}
2014-02-02 19:31:06 -05:00
s . _compatibilitySetUp ( ) ;
2014-01-03 13:32:13 -05:00
// playing this inside of a touch event will enable audio on iOS, which starts muted
s . playEmptySound ( ) ;
2014-02-02 19:31:06 -05:00
s . _capabilities = {
2014-01-03 13:32:13 -05:00
panning : true ,
volume : true ,
tracks : - 1
} ;
// determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS
var supportedExtensions = createjs . Sound . SUPPORTED _EXTENSIONS ;
var extensionMap = createjs . Sound . EXTENSION _MAP ;
for ( var i = 0 , l = supportedExtensions . length ; i < l ; i ++ ) {
var ext = supportedExtensions [ i ] ;
var playType = extensionMap [ ext ] || ext ;
2014-02-02 19:31:06 -05:00
s . _capabilities [ ext ] = ( t . canPlayType ( "audio/" + ext ) != "no" && t . canPlayType ( "audio/" + ext ) != "" ) || ( t . canPlayType ( "audio/" + playType ) != "no" && t . canPlayType ( "audio/" + playType ) != "" ) ;
2014-01-03 13:32:13 -05:00
} // OJR another way to do this might be canPlayType:"m4a", codex: mp4
// 0=no output, 1=mono, 2=stereo, 4=surround, 6=5.1 surround.
// See http://www.w3.org/TR/webaudio/#AudioChannelSplitter for more details on channels.
if ( s . context . destination . numberOfChannels < 2 ) {
2014-02-02 19:31:06 -05:00
s . _capabilities . panning = false ;
2014-01-03 13:32:13 -05:00
}
} ;
/ * *
* Set up compatibility if only deprecated web audio calls are supported .
* See http : //www.w3.org/TR/webaudio/#DeprecationNotes
* Needed so we can support new browsers that don ' t support deprecated calls ( Firefox ) as well as old browsers that
* don ' t support new calls .
*
2014-02-02 19:31:06 -05:00
* @ method _compatibilitySetUp
2014-11-18 18:26:26 -05:00
* @ static
2014-01-03 13:32:13 -05:00
* @ protected
* @ since 0.4 . 2
* /
2014-02-02 19:31:06 -05:00
s . _compatibilitySetUp = function ( ) {
2014-11-18 18:26:26 -05:00
s . _panningModel = "equalpower" ;
2014-01-03 13:32:13 -05:00
//assume that if one new call is supported, they all are
if ( s . context . createGain ) { return ; }
// simple name change, functionality the same
s . context . createGain = s . context . createGainNode ;
// source node, add to prototype
var audioNode = s . context . createBufferSource ( ) ;
audioNode . _ _proto _ _ . start = audioNode . _ _proto _ _ . noteGrainOn ; // note that noteGrainOn requires all 3 parameters
audioNode . _ _proto _ _ . stop = audioNode . _ _proto _ _ . noteOff ;
// panningModel
2014-11-18 18:26:26 -05:00
s . _panningModel = 0 ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Plays an empty sound in the web audio context . This is used to enable web audio on iOS devices , as they
* require the first sound to be played inside of a user initiated event ( touch / click ) . This is called when
* { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } i s i n i t i a l i z e d ( b y S o u n d { { # c r o s s L i n k " S o u n d / i n i t i a l i z e D e f a u l t P l u g i n s " } } { { / c r o s s L i n k } }
* for example ) .
*
* < h4 > Example < / h 4 >
* function handleTouch ( event ) {
* createjs . WebAudioPlugin . playEmptySound ( ) ;
* }
*
* @ method playEmptySound
2014-11-18 18:26:26 -05:00
* @ static
2014-01-03 13:32:13 -05:00
* @ since 0.4 . 1
* /
s . playEmptySound = function ( ) {
2014-11-18 18:26:26 -05:00
var source = s . context . createBufferSource ( ) ;
source . buffer = s . context . createBuffer ( 1 , 1 , 22050 ) ;
source . connect ( s . context . destination ) ;
2014-01-03 13:32:13 -05:00
source . start ( 0 , 0 , 0 ) ;
} ;
var p = WebAudioPlugin . prototype ;
2014-11-18 18:26:26 -05:00
p . constructor = WebAudioPlugin ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
p . _capabilities = null ; // doc'd above
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* The internal master volume value of the plugin .
* @ property _volume
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 1
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _volume = 1 ;
2014-01-03 13:32:13 -05:00
/ * *
* The web audio context , which WebAudio uses to play audio . All nodes that interact with the WebAudioPlugin
* need to be created within this context .
* @ property context
* @ type { AudioContext }
* /
p . context = null ;
/ * *
* Value to set panning model to equal power for SoundInstance . Can be "equalpower" or 0 depending on browser implementation .
2014-02-02 19:31:06 -05:00
* @ property _panningModel
2014-01-03 13:32:13 -05:00
* @ type { Number / String }
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _panningModel = "equalpower" ;
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* A DynamicsCompressorNode , which is used to improve sound quality and prevent audio distortion .
* It is connected to < code > context . destination < / c o d e > .
2014-11-18 18:26:26 -05:00
*
* Can be accessed by advanced users through createjs . Sound . activePlugin . dynamicsCompressorNode .
2014-01-03 13:32:13 -05:00
* @ property dynamicsCompressorNode
* @ type { AudioNode }
* /
p . dynamicsCompressorNode = null ;
/ * *
2014-11-18 18:26:26 -05:00
* A GainNode for controlling master volume . It is connected to { { # crossLink "WebAudioPlugin/dynamicsCompressorNode:property" } } { { / c r o s s L i n k } } .
*
* Can be accessed by advanced users through createjs . Sound . activePlugin . gainNode .
2014-01-03 13:32:13 -05:00
* @ property gainNode
* @ type { AudioGainNode }
* /
p . gainNode = null ;
/ * *
2014-11-18 18:26:26 -05:00
* An object hash used internally to store ArrayBuffers , indexed by the source URI used to load it . This
2014-01-03 13:32:13 -05:00
* prevents having to load and decode audio files more than once . If a load has been started on a file ,
* < code > arrayBuffers [ src ] < / c o d e > w i l l b e s e t t o t r u e . O n c e l o a d i s c o m p l e t e , i t i s s e t t h e t h e l o a d e d
* ArrayBuffer instance .
2014-02-02 19:31:06 -05:00
* @ property _arrayBuffers
2014-01-03 13:32:13 -05:00
* @ type { Object }
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _arrayBuffers = null ;
2014-01-03 13:32:13 -05:00
/ * *
* An initialization function run by the constructor
2014-02-02 19:31:06 -05:00
* @ method _init
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _init = function ( ) {
this . _capabilities = s . _capabilities ;
this . _arrayBuffers = { } ;
2014-01-03 13:32:13 -05:00
this . context = s . context ;
2014-11-18 18:26:26 -05:00
this . _panningModel = s . _panningModel ;
// set up AudioNodes that all of our source audio will connect to
this . dynamicsCompressorNode = this . context . createDynamicsCompressor ( ) ;
this . dynamicsCompressorNode . connect ( this . context . destination ) ;
this . gainNode = this . context . createGain ( ) ;
this . gainNode . connect ( this . dynamicsCompressorNode ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Pre - register a sound for preloading and setup . This is called by { { # crossLink "Sound" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* Note that WebAudio provides a < code > Loader < /code> instance, which <a href="http:/ / preloadjs . com " target=" _blank " > PreloadJS < / a >
2014-01-03 13:32:13 -05:00
* can use to assist with preloading .
* @ method register
* @ param { String } src The source of the audio
* @ param { Number } instances The number of concurrently playing instances to allow for the channel at any time .
* Note that the WebAudioPlugin does not manage this property .
* @ return { Object } A result object , containing a "tag" for preloading purposes .
* /
p . register = function ( src , instances ) {
2014-11-18 18:26:26 -05:00
this . _arrayBuffers [ src ] = true ;
var loader = { tag : new createjs . WebAudioPlugin . Loader ( src , this ) } ;
return loader ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Checks if preloading has started for a specific source . If the source is found , we can assume it is loading ,
* or has already finished loading .
* @ method isPreloadStarted
* @ param { String } src The sound URI to check .
* @ return { Boolean }
* /
p . isPreloadStarted = function ( src ) {
2014-02-02 19:31:06 -05:00
return ( this . _arrayBuffers [ src ] != null ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Checks if preloading has finished for a specific source .
* @ method isPreloadComplete
* @ param { String } src The sound URI to load .
* @ return { Boolean }
* /
p . isPreloadComplete = function ( src ) {
2014-02-02 19:31:06 -05:00
return ( ! ( this . _arrayBuffers [ src ] == null || this . _arrayBuffers [ src ] == true ) ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Remove a sound added using { { # crossLink "WebAudioPlugin/register" } } { { / c r o s s L i n k } } . N o t e t h i s d o e s n o t c a n c e l a p r e l o a d .
* @ method removeSound
* @ param { String } src The sound URI to unload .
* @ since 0.4 . 1
* /
p . removeSound = function ( src ) {
2014-02-02 19:31:06 -05:00
delete ( this . _arrayBuffers [ src ] ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Remove all sounds added using { { # crossLink "WebAudioPlugin/register" } } { { / c r o s s L i n k } } . N o t e t h i s d o e s n o t c a n c e l a p r e l o a d .
* @ method removeAllSounds
* @ param { String } src The sound URI to unload .
* @ since 0.4 . 1
* /
p . removeAllSounds = function ( ) {
2014-02-02 19:31:06 -05:00
this . _arrayBuffers = { } ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Add loaded results to the preload object hash .
* @ method addPreloadResults
* @ param { String } src The sound URI to unload .
* @ return { Boolean }
* /
p . addPreloadResults = function ( src , result ) {
2014-02-02 19:31:06 -05:00
this . _arrayBuffers [ src ] = result ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Handles internal preload completion .
2014-02-02 19:31:06 -05:00
* @ method _handlePreloadComplete
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-11-18 18:26:26 -05:00
p . _handlePreloadComplete = function ( loader ) {
createjs . Sound . _sendFileLoadEvent ( loader . src ) ;
loader . cleanUp ( ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Internally preload a sound . Loading uses XHR2 to load an array buffer for use with WebAudio .
* @ method preload
* @ param { String } src The sound URI to load .
2014-11-18 18:26:26 -05:00
* @ param { Object } tag Not used in this plugin .
2014-01-03 13:32:13 -05:00
* /
2014-11-18 18:26:26 -05:00
p . preload = function ( src , tag ) {
2014-02-02 19:31:06 -05:00
this . _arrayBuffers [ src ] = true ;
2014-01-03 13:32:13 -05:00
var loader = new createjs . WebAudioPlugin . Loader ( src , this ) ;
2014-02-02 19:31:06 -05:00
loader . onload = this . _handlePreloadComplete ;
2014-01-03 13:32:13 -05:00
loader . load ( ) ;
} ;
/ * *
* Create a sound instance . If the sound has not been preloaded , it is internally preloaded here .
* @ method create
* @ param { String } src The sound source to use .
2014-11-18 18:26:26 -05:00
* @ 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 .
2014-01-03 13:32:13 -05:00
* @ return { SoundInstance } A sound instance for playback and control .
* /
2014-11-18 18:26:26 -05:00
p . create = function ( src , startTime , duration ) {
if ( ! this . isPreloadStarted ( src ) ) { this . preload ( src ) ; }
return new createjs . WebAudioPlugin . SoundInstance ( src , startTime , duration , this ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Set the master volume of the plugin , which affects all SoundInstances .
* @ method setVolume
* @ param { Number } value The volume to set , between 0 and 1.
* @ return { Boolean } If the plugin processes the setVolume call ( true ) . The Sound class will affect all the
* instances manually otherwise .
* /
p . setVolume = function ( value ) {
2014-02-02 19:31:06 -05:00
this . _volume = value ;
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Set the gain value for master audio . Should not be called externally .
2014-02-02 19:31:06 -05:00
* @ method _updateVolume
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _updateVolume = function ( ) {
var newVolume = createjs . Sound . _masterMute ? 0 : this . _volume ;
2014-01-03 13:32:13 -05:00
if ( newVolume != this . gainNode . gain . value ) {
this . gainNode . gain . value = newVolume ;
}
} ;
/ * *
* Get the master volume of the plugin , which affects all SoundInstances .
* @ method getVolume
* @ return The volume level , between 0 and 1.
* /
p . getVolume = function ( ) {
2014-02-02 19:31:06 -05:00
return this . _volume ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Mute all sounds via the plugin .
* @ method setMute
* @ param { Boolean } value If all sound should be muted or not . Note that plugin - level muting just looks up
* the mute value of Sound { { # crossLink "Sound/getMute" } } { { / c r o s s L i n k } } , s o t h i s p r o p e r t y i s n o t u s e d h e r e .
* @ return { Boolean } If the mute call succeeds .
* /
p . setMute = function ( value ) {
2014-02-02 19:31:06 -05:00
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
p . toString = function ( ) {
return "[WebAudioPlugin]" ;
} ;
createjs . WebAudioPlugin = WebAudioPlugin ;
} ( ) ) ;
( function ( ) {
"use strict" ;
/ * *
* A SoundInstance is created when any calls to the Sound API method { { # crossLink "Sound/play" } } { { / c r o s s L i n k } } o r
* { { # crossLink "Sound/createInstance" } } { { / c r o s s L i n k } } a r e m a d e . T h e S o u n d I n s t a n c e i s r e t u r n e d b y t h e a c t i v e p l u g i n
* for control by the user .
*
* < h4 > Example < / h 4 >
* var myInstance = createjs . Sound . play ( "myAssetPath/mySrcFile.mp3" ) ;
*
* A number of additional parameters provide a quick way to determine how a sound is played . Please see the Sound
* API method { { # crossLink "Sound/play" } } { { / c r o s s L i n k } } f o r a l i s t o f a r g u m e n t s .
*
* Once a SoundInstance is created , a reference can be stored that can be used to control the audio directly through
* the SoundInstance . If the reference is not stored , the SoundInstance will play out its audio ( and any loops ) , and
* is then de - referenced from the { { # crossLink "Sound" } } { { / c r o s s L i n k } } c l a s s s o t h a t i t c a n b e c l e a n e d u p . I f a u d i o
* playback has completed , a simple call to the { { # crossLink "SoundInstance/play" } } { { / c r o s s L i n k } } i n s t a n c e m e t h o d
* will rebuild the references the Sound class need to control it .
*
2014-02-02 19:31:06 -05:00
* var myInstance = createjs . Sound . play ( "myAssetPath/mySrcFile.mp3" , { loop : 2 } ) ;
* myInstance . addEventListener ( "loop" , handleLoop ) ;
* function handleLoop ( event ) {
* myInstance . volume = myInstance . volume * 0.5 ;
2014-01-03 13:32:13 -05:00
* }
*
* Events are dispatched from the instance to notify when the sound has completed , looped , or when playback fails
*
* var myInstance = createjs . Sound . play ( "myAssetPath/mySrcFile.mp3" ) ;
* myInstance . addEventListener ( "complete" , handleComplete ) ;
* myInstance . addEventListener ( "loop" , handleLoop ) ;
* myInstance . addEventListener ( "failed" , handleFailed ) ;
*
*
* @ class SoundInstance
* @ param { String } src The path to and file name of the sound .
2014-11-18 18:26:26 -05:00
* @ 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 .
2014-01-03 13:32:13 -05:00
* @ param { Object } owner The plugin instance that created this SoundInstance .
* @ extends EventDispatcher
* @ constructor
* /
2014-11-18 18:26:26 -05:00
function SoundInstance ( src , startTime , duration , owner ) {
this . _init ( src , startTime , duration , owner ) ;
2014-01-03 13:32:13 -05:00
}
var p = SoundInstance . prototype = new createjs . EventDispatcher ( ) ;
2014-11-18 18:26:26 -05:00
p . constructor = SoundInstance ;
2014-01-03 13:32:13 -05:00
/ * *
* The source of the sound .
* @ property src
* @ type { String }
* @ default null
* /
p . src = null ;
/ * *
* The unique ID of the instance . This is set by { { # crossLink "Sound" } } { { / c r o s s L i n k } } .
* @ property uniqueId
* @ type { String } | Number
* @ default - 1
* /
p . uniqueId = - 1 ;
/ * *
* The play state of the sound . Play states are defined as constants on { { # crossLink "Sound" } } { { / c r o s s L i n k } } .
* @ property playState
* @ type { String }
* @ default null
* /
p . playState = null ;
/ * *
* The plugin that created the instance
2014-02-02 19:31:06 -05:00
* @ property _owner
2014-01-03 13:32:13 -05:00
* @ type { WebAudioPlugin }
* @ default null
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _owner = null ;
2014-01-03 13:32:13 -05:00
/ * *
* How far into the sound to begin playback in milliseconds . This is passed in when play is called and used by
* pause and setPosition to track where the sound should be at .
* Note this is converted from milliseconds to seconds for consistency with the WebAudio API .
2014-02-02 19:31:06 -05:00
* @ property _offset
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _offset = 0 ;
2014-01-03 13:32:13 -05:00
/ * *
2014-11-18 18:26:26 -05:00
* Audio sprite property used to determine the starting offset .
2014-01-03 13:32:13 -05:00
* @ type { Number }
2014-11-18 18:26:26 -05:00
* @ default null
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-11-18 18:26:26 -05:00
p . _startTime = 0 ;
2014-01-03 13:32:13 -05:00
/ * *
* The volume of the sound , between 0 and 1.
2014-02-02 19:31:06 -05:00
* < br / > Note this uses a getter setter , which is not supported by Firefox versions 3.6 or lower and Opera versions 11.50 or lower ,
2014-01-03 13:32:13 -05:00
* and Internet Explorer 8 or lower . Instead use { { # crossLink "SoundInstance/setVolume" } } { { / c r o s s L i n k } } a n d { { # c r o s s L i n k " S o u n d I n s t a n c e / g e t V o l u m e " } } { { / c r o s s L i n k } } .
*
* The actual output volume of a sound can be calculated using :
* < code > myInstance . volume * createjs . Sound . getVolume ( ) ; < / c o d e >
*
* @ property volume
* @ type { Number }
* @ default 1
* /
p . _volume = 1 ;
2014-11-18 18:26:26 -05:00
if ( createjs . definePropertySupported ) {
2014-01-03 13:32:13 -05:00
Object . defineProperty ( p , "volume" , {
get : function ( ) {
return this . _volume ;
} ,
set : function ( value ) {
if ( Number ( value ) == null ) { return false }
value = Math . max ( 0 , Math . min ( 1 , value ) ) ;
this . _volume = value ;
2014-02-02 19:31:06 -05:00
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
}
} ) ;
2014-11-18 18:26:26 -05:00
}
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* The pan of the sound , between - 1 ( left ) and 1 ( right ) . Note that pan is not supported by HTML Audio .
*
* < br / > Note this uses a getter setter , which is not supported by Firefox versions 3.6 or lower , Opera versions 11.50 or lower ,
2014-01-03 13:32:13 -05:00
* and Internet Explorer 8 or lower . Instead use { { # crossLink "SoundInstance/setPan" } } { { / c r o s s L i n k } } a n d { { # c r o s s L i n k " S o u n d I n s t a n c e / g e t P a n " } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* < br / > Note in WebAudioPlugin this only gives us the "x" value of what is actually 3 D audio .
2014-01-03 13:32:13 -05:00
*
* @ property pan
* @ type { Number }
* @ default 0
* /
p . _pan = 0 ;
2014-11-18 18:26:26 -05:00
if ( createjs . definePropertySupported ) {
2014-01-03 13:32:13 -05:00
Object . defineProperty ( p , "pan" , {
get : function ( ) {
return this . _pan ;
} ,
set : function ( value ) {
2014-02-02 19:31:06 -05:00
if ( ! this . _owner . _capabilities . panning || Number ( value ) == null ) { return false ; }
2014-01-03 13:32:13 -05:00
value = Math . max ( - 1 , Math . min ( 1 , value ) ) ; // force pan to stay in the -1 to 1 range
// Note that panning in WebAudioPlugin can support 3D audio, but our implementation does not.
this . _pan = value ; // Unfortunately panner does not give us a way to access this after it is set http://www.w3.org/TR/webaudio/#AudioPannerNode
this . panNode . setPosition ( value , 0 , - 0.5 ) ; // z need to be -0.5 otherwise the sound only plays in left, right, or center
}
} ) ;
2014-11-18 18:26:26 -05:00
}
2014-01-03 13:32:13 -05:00
/ * *
* The length of the audio clip , in milliseconds .
* Use { { # crossLink "SoundInstance/getDuration:method" } } { { / c r o s s L i n k } } t o a c c e s s .
2014-02-02 19:31:06 -05:00
* @ property _duration
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _duration = 0 ;
2014-01-03 13:32:13 -05:00
/ * *
* The number of play loops remaining . Negative values will loop infinitely .
2014-11-18 18:26:26 -05:00
*
* @ property loop
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
2014-11-18 18:26:26 -05:00
* @ public
2014-01-03 13:32:13 -05:00
* /
2014-02-02 19:31:06 -05:00
p . _remainingLoops = 0 ;
2014-11-18 18:26:26 -05:00
if ( createjs . definePropertySupported ) {
Object . defineProperty ( p , "loop" , {
get : function ( ) {
return this . _remainingLoops ;
} ,
set : function ( value ) {
// remove looping
if ( this . _remainingLoops != 0 && value == 0 ) {
this . _sourceNodeNext = this . _cleanUpAudioNode ( this . _sourceNodeNext ) ;
}
// add looping
if ( this . _remainingLoops == 0 && value != 0 ) {
this . _sourceNodeNext = this . _createAndPlayAudioNode ( this . _playbackStartTime , 0 ) ;
}
this . _remainingLoops = value ;
}
} ) ;
}
2014-01-03 13:32:13 -05:00
/ * *
* A Timeout created by { { # crossLink "Sound" } } { { / c r o s s L i n k } } w h e n t h i s S o u n d I n s t a n c e i s p l a y e d w i t h a d e l a y .
2014-11-18 18:26:26 -05:00
* This allows SoundInstance to remove the delay if stop , pause , or cleanup are called before playback begins .
2014-02-02 19:31:06 -05:00
* @ property _delayTimeoutId
2014-01-03 13:32:13 -05:00
* @ type { timeoutVariable }
* @ default null
* @ protected
* @ since 0.4 . 0
* /
2014-02-02 19:31:06 -05:00
p . _delayTimeoutId = null ;
2014-01-03 13:32:13 -05:00
/ * *
* Timeout that is created internally to handle sound playing to completion . Stored so we can remove it when
* stop , pause , or cleanup are called
2014-02-02 19:31:06 -05:00
* @ property _soundCompleteTimeout
2014-01-03 13:32:13 -05:00
* @ type { timeoutVariable }
* @ default null
* @ protected
* @ since 0.4 . 0
* /
2014-02-02 19:31:06 -05:00
p . _soundCompleteTimeout = null ;
2014-01-03 13:32:13 -05:00
/ * *
* NOTE this only exists as a { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } p r o p e r t y a n d i s o n l y i n t e n d e d f o r u s e b y a d v a n c e d u s e r s .
2014-02-02 19:31:06 -05:00
* < br / > GainNode for controlling < code > SoundInstance < / c o d e > v o l u m e . C o n n e c t e d t o t h e W e b A u d i o P l u g i n { { # c r o s s L i n k " W e b A u d i o P l u g i n / g a i n N o d e : p r o p e r t y " } } { { / c r o s s L i n k } }
2014-01-03 13:32:13 -05:00
* that sequences to < code > context . destination < / c o d e > .
* @ property gainNode
* @ type { AudioGainNode }
* @ since 0.4 . 0
*
* /
p . gainNode = null ;
/ * *
* NOTE this only exists as a { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } p r o p e r t y a n d i s o n l y i n t e n d e d f o r u s e b y a d v a n c e d u s e r s .
2014-02-02 19:31:06 -05:00
* < br / > A panNode allowing left and right audio channel panning only . Connected to SoundInstance { { # crossLink "SoundInstance/gainNode:property" } } { { / c r o s s L i n k } } .
2014-01-03 13:32:13 -05:00
* @ property panNode
* @ type { AudioPannerNode }
* @ since 0.4 . 0
* /
p . panNode = null ;
/ * *
* NOTE this only exists as a { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } p r o p e r t y a n d i s o n l y i n t e n d e d f o r u s e b y a d v a n c e d u s e r s .
2014-02-02 19:31:06 -05:00
* < br / > sourceNode is the audio source . Connected to SoundInstance { { # crossLink "SoundInstance/panNode:property" } } { { / c r o s s L i n k } } .
2014-01-03 13:32:13 -05:00
* @ property sourceNode
* @ type { AudioNode }
* @ since 0.4 . 0
*
* /
p . sourceNode = null ;
/ * *
* NOTE this only exists as a { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } p r o p e r t y a n d i s o n l y i n t e n d e d f o r u s e b y a d v a n c e d u s e r s .
2014-02-02 19:31:06 -05:00
* _sourceNodeNext is the audio source for the next loop , inserted in a look ahead approach to allow for smooth
2014-01-03 13:32:13 -05:00
* looping . Connected to { { # crossLink "SoundInstance/gainNode:property" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* @ property _sourceNodeNext
2014-01-03 13:32:13 -05:00
* @ type { AudioNode }
* @ default null
* @ protected
* @ since 0.4 . 1
*
* /
2014-02-02 19:31:06 -05:00
p . _sourceNodeNext = null ;
2014-01-03 13:32:13 -05:00
/ * *
* Determines if the audio is currently muted .
* Use { { # crossLink "SoundInstance/getMute:method" } } { { / c r o s s L i n k } } a n d { { # c r o s s L i n k " S o u n d I n s t a n c e / s e t M u t e : m e t h o d " } } { { / c r o s s L i n k } } t o a c c e s s .
2014-02-02 19:31:06 -05:00
* @ property _muted
2014-01-03 13:32:13 -05:00
* @ type { Boolean }
* @ default false
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _muted = false ;
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* Read only value that tells you if the audio is currently paused .
2014-01-03 13:32:13 -05:00
* Use { { # crossLink "SoundInstance/pause:method" } } { { / c r o s s L i n k } } a n d { { # c r o s s L i n k " S o u n d I n s t a n c e / r e s u m e : m e t h o d " } } { { / c r o s s L i n k } } t o s e t .
* @ property paused
* @ type { Boolean }
* /
2014-02-02 19:31:06 -05:00
p . paused = false ; // this value will not be used, and is only set
p . _paused = false ; // this value is used internally for setting paused
2014-01-03 13:32:13 -05:00
/ * *
* WebAudioPlugin only .
* Time audio started playback , in seconds . Used to handle set position , get position , and resuming from paused .
2014-11-18 18:26:26 -05:00
* @ property _playbackStartTime
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
* @ protected
* @ since 0.4 . 0
* /
2014-11-18 18:26:26 -05:00
p . _playbackStartTime = 0 ;
2014-01-03 13:32:13 -05:00
// Proxies, make removing listeners easier.
2014-02-02 19:31:06 -05:00
p . _endedHandler = null ;
2014-01-03 13:32:13 -05:00
// Events
/ * *
* The event that is fired when playback has started successfully .
* @ event succeeded
* @ param { Object } target The object that dispatched the event .
* @ param { String } type The event type .
* @ since 0.4 . 0
* /
/ * *
* The event that is fired when playback is interrupted . This happens when another sound with the same
* src property is played using an interrupt value that causes this instance to stop playing .
* @ event interrupted
* @ param { Object } target The object that dispatched the event .
* @ param { String } type The event type .
* @ since 0.4 . 0
* /
/ * *
* The event that is fired when playback has failed . This happens when there are too many channels with the same
* src property already playing ( and the interrupt value doesn ' t cause an interrupt of another instance ) , or
* the sound could not be played , perhaps due to a 404 error .
* @ event failed
* @ param { Object } target The object that dispatched the event .
* @ param { String } type The event type .
* @ since 0.4 . 0
* /
/ * *
* The event that is fired when a sound has completed playing but has loops remaining .
* @ event loop
* @ param { Object } target The object that dispatched the event .
* @ param { String } type The event type .
* @ since 0.4 . 0
* /
/ * *
* The event that is fired when playback completes . This means that the sound has finished playing in its
* entirety , including its loop iterations .
* @ event complete
* @ param { Object } target The object that dispatched the event .
* @ param { String } type The event type .
* @ since 0.4 . 0
* /
/ * *
* A helper method that dispatches all events for SoundInstance .
2014-02-02 19:31:06 -05:00
* @ method _sendEvent
2014-01-03 13:32:13 -05:00
* @ param { String } type The event type
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _sendEvent = function ( type ) {
2014-01-03 13:32:13 -05:00
var event = new createjs . Event ( type ) ;
this . dispatchEvent ( event ) ;
} ;
// Constructor
/ * *
* Initialize the SoundInstance . This is called from the constructor .
2014-02-02 19:31:06 -05:00
* @ method _init
2014-01-03 13:32:13 -05:00
* @ param { string } src The source of the audio .
2014-11-18 18:26:26 -05:00
* @ 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 .
2014-01-03 13:32:13 -05:00
* @ param { Class } owner The plugin that created this instance .
* @ protected
* /
2014-11-18 18:26:26 -05:00
p . _init = function ( src , startTime , duration , owner ) {
2014-01-03 13:32:13 -05:00
this . src = src ;
2014-11-18 18:26:26 -05:00
this . _startTime = startTime * 0.001 || 0 ; // convert ms to s as web audio handles everything in seconds
this . _duration = duration || 0 ;
this . _owner = owner ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . gainNode = this . _owner . context . createGain ( ) ;
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
this . panNode = this . _owner . context . createPanner ( ) ;
2014-02-02 19:31:06 -05:00
this . panNode . panningModel = this . _owner . _panningModel ;
2014-01-03 13:32:13 -05:00
this . panNode . connect ( this . gainNode ) ;
2014-11-18 18:26:26 -05:00
if ( this . _owner . isPreloadComplete ( this . src ) && ! this . _duration ) { this . _duration = this . _owner . _arrayBuffers [ this . src ] . duration * 1000 ; }
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . _endedHandler = createjs . proxy ( this . _handleSoundComplete , this ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Clean up the instance . Remove references and clean up any additional properties such as timers .
2014-02-02 19:31:06 -05:00
* @ method _cleanUp
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _cleanUp = function ( ) {
2014-01-03 13:32:13 -05:00
if ( this . sourceNode && this . playState == createjs . Sound . PLAY _SUCCEEDED ) {
2014-02-02 19:31:06 -05:00
this . sourceNode = this . _cleanUpAudioNode ( this . sourceNode ) ;
this . _sourceNodeNext = this . _cleanUpAudioNode ( this . _sourceNodeNext ) ;
2014-01-03 13:32:13 -05:00
}
2014-11-18 18:26:26 -05:00
if ( this . gainNode . numberOfOutputs != 0 ) { this . gainNode . disconnect ( 0 ) ; }
2014-01-03 13:32:13 -05:00
// OJR there appears to be a bug that this doesn't always work in webkit (Chrome and Safari). According to the documentation, this should work.
2014-02-02 19:31:06 -05:00
clearTimeout ( this . _delayTimeoutId ) ; // clear timeout that plays delayed sound
clearTimeout ( this . _soundCompleteTimeout ) ; // clear timeout that triggers sound complete
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
this . _playbackStartTime = 0 ; // This is used by getPosition
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
createjs . Sound . _playFinished ( this ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Turn off and disconnect an audioNode , then set reference to null to release it for garbage collection
2014-02-02 19:31:06 -05:00
* @ method _cleanUpAudioNode
2014-01-03 13:32:13 -05:00
* @ param audioNode
* @ return { audioNode }
* @ protected
* @ since 0.4 . 1
* /
2014-02-02 19:31:06 -05:00
p . _cleanUpAudioNode = function ( audioNode ) {
2014-01-03 13:32:13 -05:00
if ( audioNode ) {
audioNode . stop ( 0 ) ;
2014-11-18 18:26:26 -05:00
audioNode . disconnect ( 0 ) ;
audioNode = null ;
2014-01-03 13:32:13 -05:00
}
return audioNode ;
} ;
/ * *
* The sound has been interrupted .
2014-02-02 19:31:06 -05:00
* @ method _interrupt
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _interrupt = function ( ) {
this . _cleanUp ( ) ;
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _INTERRUPTED ;
2014-02-02 19:31:06 -05:00
this . paused = this . _paused = false ;
this . _sendEvent ( "interrupted" ) ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
/ * *
* Handles starting playback when the sound is ready for playing .
* @ method _handleSoundReady
* @ protected
* /
p . _handleSoundReady = function ( event ) {
2014-11-18 18:26:26 -05:00
if ( ! this . _duration ) { this . _duration = this . _owner . _arrayBuffers [ this . src ] . duration * 1000 ; } // NOTE *1000 because WebAudio reports everything in seconds but js uses milliseconds
if ( ( this . _offset * 1000 ) > this . _duration ) {
2014-01-03 13:32:13 -05:00
this . playFailed ( ) ;
return ;
2014-02-02 19:31:06 -05:00
} else if ( this . _offset < 0 ) { // may not need this check if play ignores negative values, this is not specified in the API http://www.w3.org/TR/webaudio/#AudioBufferSourceNode
this . _offset = 0 ;
2014-01-03 13:32:13 -05:00
}
this . playState = createjs . Sound . PLAY _SUCCEEDED ;
2014-02-02 19:31:06 -05:00
this . paused = this . _paused = false ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . gainNode . connect ( this . _owner . gainNode ) ; // this line can cause a memory leak. Nodes need to be disconnected from the audioDestination or any sequence that leads to it.
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
var dur = this . _duration * 0.001 ;
2014-02-02 19:31:06 -05:00
this . sourceNode = this . _createAndPlayAudioNode ( ( this . _owner . context . currentTime - dur ) , this . _offset ) ;
2014-11-18 18:26:26 -05:00
this . _playbackStartTime = this . sourceNode . startTime - this . _offset ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . _soundCompleteTimeout = setTimeout ( this . _endedHandler , ( dur - this . _offset ) * 1000 ) ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
if ( this . _remainingLoops != 0 ) {
2014-11-18 18:26:26 -05:00
this . _sourceNodeNext = this . _createAndPlayAudioNode ( this . _playbackStartTime , 0 ) ;
2014-01-03 13:32:13 -05:00
}
} ;
/ * *
* Creates an audio node using the current src and context , connects it to the gain node , and starts playback .
2014-02-02 19:31:06 -05:00
* @ method _createAndPlayAudioNode
2014-01-03 13:32:13 -05:00
* @ param { Number } startTime The time to add this to the web audio context , in seconds .
* @ param { Number } offset The amount of time into the src audio to start playback , in seconds .
* @ return { audioNode }
* @ protected
* @ since 0.4 . 1
* /
2014-02-02 19:31:06 -05:00
p . _createAndPlayAudioNode = function ( startTime , offset ) {
var audioNode = this . _owner . context . createBufferSource ( ) ;
audioNode . buffer = this . _owner . _arrayBuffers [ this . src ] ;
2014-01-03 13:32:13 -05:00
audioNode . connect ( this . panNode ) ;
2014-11-18 18:26:26 -05:00
var dur = this . _duration * 0.001 ;
audioNode . startTime = startTime + dur ;
audioNode . start ( audioNode . startTime , offset + this . _startTime , dur - offset ) ;
2014-01-03 13:32:13 -05:00
return audioNode ;
} ;
// Public API
/ * *
* Play an instance . This method is intended to be called on SoundInstances that already exist ( created
* with the Sound API { { # crossLink "Sound/createInstance" } } { { / c r o s s L i n k } } o r { { # c r o s s L i n k " S o u n d / p l a y " } } { { / c r o s s L i n k } } ) .
*
* < h4 > Example < / h 4 >
* var myInstance = createjs . Sound . createInstance ( mySrc ) ;
2014-02-02 19:31:06 -05:00
* myInstance . play ( { offset : 1 , loop : 2 , pan : 0.5 } ) ; // options as object properties
* myInstance . play ( createjs . Sound . INTERRUPT _ANY ) ; // options as parameters
2014-01-03 13:32:13 -05:00
*
2014-11-18 18:26:26 -05:00
* Note that if this sound is already playing , this call will do nothing .
*
2014-01-03 13:32:13 -05:00
* @ method play
* @ param { String | Object } [ interrupt = "none" | options ] How to interrupt any currently playing instances of audio with the same source ,
* if the maximum number of instances of the sound are already playing . Values are defined as < code > INTERRUPT _TYPE < / c o d e >
2014-02-02 19:31:06 -05:00
* constants on the Sound class , with the default defined by Sound { { # crossLink "Sound/defaultInterruptBehavior:property" } } { { / c r o s s L i n k } } .
2014-01-03 13:32:13 -05:00
* < br / > < strong > OR < /strong><br / >
* This parameter can be an object that contains any or all optional properties by name , including : interrupt ,
* delay , offset , loop , volume , and pan ( see the above code sample ) .
* @ param { Number } [ delay = 0 ] The delay in milliseconds before the sound starts
* @ param { Number } [ offset = 0 ] How far into the sound to begin playback , in milliseconds .
* @ param { Number } [ loop = 0 ] The number of times to loop the audio . Use - 1 for infinite loops .
* @ param { Number } [ volume = 1 ] The volume of the sound , between 0 and 1.
* @ param { Number } [ pan = 0 ] The pan of the sound between - 1 ( left ) and 1 ( right ) . Note that pan is not supported
* for HTML Audio .
* /
p . play = function ( interrupt , delay , offset , loop , volume , pan ) {
2014-11-18 18:26:26 -05:00
if ( this . playState == createjs . Sound . PLAY _SUCCEEDED ) {
if ( interrupt instanceof Object ) {
offset = interrupt . offset ;
loop = interrupt . loop ;
volume = interrupt . volume ;
pan = interrupt . pan ;
}
if ( offset != null ) { this . setPosition ( offset ) }
if ( loop != null ) { this . loop = loop ; }
if ( volume != null ) { this . setVolume ( volume ) ; }
if ( pan != null ) { this . setPan ( pan ) ; }
if ( this . _paused ) { this . resume ( ) ; }
return ;
}
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
createjs . Sound . _playInstance ( this , interrupt , delay , offset , loop , volume , pan ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Called by the Sound class when the audio is ready to play ( delay has completed ) . Starts sound playing if the
* src is loaded , otherwise playback will fail .
2014-02-02 19:31:06 -05:00
* @ method _beginPlaying
2014-01-03 13:32:13 -05:00
* @ param { Number } offset How far into the sound to begin playback , in milliseconds .
* @ param { Number } loop The number of times to loop the audio . Use - 1 for infinite loops .
* @ param { Number } volume The volume of the sound , between 0 and 1.
* @ param { Number } pan The pan of the sound between - 1 ( left ) and 1 ( right ) . Note that pan does not work for HTML Audio .
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _beginPlaying = function ( offset , loop , volume , pan ) {
2014-11-18 18:26:26 -05:00
this . _offset = offset * 0.001 ; //convert ms to sec
2014-02-02 19:31:06 -05:00
this . _remainingLoops = loop ;
2014-01-03 13:32:13 -05:00
this . volume = volume ;
this . pan = pan ;
2014-02-02 19:31:06 -05:00
if ( this . _owner . isPreloadComplete ( this . src ) ) {
this . _handleSoundReady ( null ) ;
this . _sendEvent ( "succeeded" ) ;
2014-01-03 13:32:13 -05:00
return 1 ;
} else {
this . playFailed ( ) ;
return ;
}
} ;
/ * *
* Pause the instance . Paused audio will stop at the current time , and can be resumed using
* { { # crossLink "SoundInstance/resume" } } { { / c r o s s L i n k } } .
*
* < h4 > Example < / h 4 >
*
* myInstance . pause ( ) ;
*
* @ method pause
* @ return { Boolean } If the pause call succeeds . This will return false if the sound isn ' t currently playing .
* /
p . pause = function ( ) {
2014-11-18 18:26:26 -05:00
if ( this . _paused || this . playState != createjs . Sound . PLAY _SUCCEEDED ) { return false ; }
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
this . paused = this . _paused = true ;
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
this . _offset = this . _owner . context . currentTime - this . _playbackStartTime ; // this allows us to restart the sound at the same point in playback
this . sourceNode = this . _cleanUpAudioNode ( this . sourceNode ) ;
this . _sourceNodeNext = this . _cleanUpAudioNode ( this . _sourceNodeNext ) ;
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
if ( this . gainNode . numberOfOutputs != 0 ) { this . gainNode . disconnect ( 0 ) ; }
clearTimeout ( this . _delayTimeoutId ) ;
clearTimeout ( this . _soundCompleteTimeout ) ;
return true ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Resume an instance that has been paused using { { # crossLink "SoundInstance/pause" } } { { / c r o s s L i n k } } . A u d i o t h a t
* has not been paused will not playback when this method is called .
*
* < h4 > Example < / h 4 >
*
* myInstance . pause ( ) ;
* // do some stuff
* myInstance . resume ( ) ;
*
* @ method resume
* @ return { Boolean } If the resume call succeeds . This will return false if called on a sound that is not paused .
* /
p . resume = function ( ) {
2014-11-18 18:26:26 -05:00
if ( ! this . _paused ) { return false ; }
this . _handleSoundReady ( ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Stop playback of the instance . Stopped sounds will reset their position to 0 , and calls to { { # crossLink "SoundInstance/resume" } } { { / c r o s s L i n k } }
* will fail . To start playback again , call { { # crossLink "SoundInstance/play" } } { { / c r o s s L i n k } } .
*
* < h4 > Example < / h 4 >
*
* myInstance . stop ( ) ;
*
* @ method stop
* @ return { Boolean } If the stop call succeeds .
* /
p . stop = function ( ) {
2014-02-02 19:31:06 -05:00
this . paused = this . _paused = false ;
this . _cleanUp ( ) ;
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _FINISHED ;
2014-02-02 19:31:06 -05:00
this . _offset = 0 ; // set audio to start at the beginning
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
2014-02-02 19:31:06 -05:00
* NOTE that you can set volume directly as a property , and setVolume remains to allow support for IE8 with FlashPlugin .
2014-01-03 13:32:13 -05:00
* Set the volume of the instance . You can retrieve the volume using { { # crossLink "SoundInstance/getVolume" } } { { / c r o s s L i n k } } .
*
* < h4 > Example < / h 4 >
*
* myInstance . setVolume ( 0.5 ) ;
*
* Note that the master volume set using the Sound API method { { # crossLink "Sound/setVolume" } } { { / c r o s s L i n k } }
* will be applied to the instance volume .
*
* @ method setVolume
* @ param value The volume to set , between 0 and 1.
* @ return { Boolean } If the setVolume call succeeds .
* /
p . setVolume = function ( value ) {
this . volume = value ;
2014-11-18 18:26:26 -05:00
return true ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Internal function used to update the volume based on the instance volume , master volume , instance mute value ,
* and master mute value .
2014-02-02 19:31:06 -05:00
* @ method _updateVolume
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _updateVolume = function ( ) {
var newVolume = this . _muted ? 0 : this . _volume ;
2014-01-03 13:32:13 -05:00
if ( newVolume != this . gainNode . gain . value ) {
this . gainNode . gain . value = newVolume ;
}
} ;
/ * *
2014-02-02 19:31:06 -05:00
* NOTE that you can access volume directly as a property , and getVolume remains to allow support for IE8 with FlashPlugin .
2014-01-03 13:32:13 -05:00
*
* Get the volume of the instance . The actual output volume of a sound can be calculated using :
* < code > myInstance . getVolume ( ) * createjs . Sound . getVolume ( ) ; < / c o d e >
*
* @ method getVolume
* @ return The current volume of the sound instance .
* /
p . getVolume = function ( ) {
return this . volume ;
} ;
/ * *
* Mute and unmute the sound . Muted sounds will still play at 0 volume . Note that an unmuted sound may still be
* silent depending on { { # crossLink "Sound" } } { { / c r o s s L i n k } } v o l u m e , i n s t a n c e v o l u m e , a n d S o u n d m u t e .
*
* < h4 > Example < / h 4 >
*
* myInstance . setMute ( true ) ;
*
* @ method setMute
* @ param { Boolean } value If the sound should be muted .
* @ return { Boolean } If the mute call succeeds .
* @ since 0.4 . 0
* /
p . setMute = function ( value ) {
2014-11-18 18:26:26 -05:00
if ( value == null ) { return false ; }
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . _muted = value ;
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Get the mute value of the instance .
*
* < h4 > Example < / h 4 >
*
* var isMuted = myInstance . getMute ( ) ;
*
* @ method getMute
* @ return { Boolean } If the sound is muted .
* @ since 0.4 . 0
* /
p . getMute = function ( ) {
2014-02-02 19:31:06 -05:00
return this . _muted ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
2014-02-02 19:31:06 -05:00
* NOTE that you can set pan directly as a property , and getPan remains to allow support for IE8 with FlashPlugin .
2014-01-03 13:32:13 -05:00
*
* Set the left ( - 1 ) / right ( + 1 ) pan of the instance . Note that { { # crossLink "HTMLAudioPlugin" } } { { / c r o s s L i n k } } d o e s n o t
* support panning , and only simple left / right panning has been implemented for { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } .
* The default pan value is 0 ( center ) .
*
* < h4 > Example < / h 4 >
*
* myInstance . setPan ( - 1 ) ; // to the left!
*
* @ method setPan
* @ param { Number } value The pan value , between - 1 ( left ) and 1 ( right ) .
* @ return { Number } If the setPan call succeeds .
* /
p . setPan = function ( value ) {
this . pan = value ; // Unfortunately panner does not give us a way to access this after it is set http://www.w3.org/TR/webaudio/#AudioPannerNode
if ( this . pan != value ) { return false ; }
2014-11-18 18:26:26 -05:00
return true ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
2014-02-02 19:31:06 -05:00
* NOTE that you can access pan directly as a property , and getPan remains to allow support for IE8 with FlashPlugin .
2014-01-03 13:32:13 -05:00
*
* Get the left / right pan of the instance . Note in WebAudioPlugin this only gives us the "x" value of what is
* actually 3 D audio .
*
* < h4 > Example < / h 4 >
*
* var myPan = myInstance . getPan ( ) ;
*
* @ method getPan
* @ return { Number } The value of the pan , between - 1 ( left ) and 1 ( right ) .
* /
p . getPan = function ( ) {
return this . pan ;
} ;
/ * *
2014-02-02 19:31:06 -05:00
* Get the position of the playhead of the instance in milliseconds .
2014-01-03 13:32:13 -05:00
*
* < h4 > Example < / h 4 >
*
* var currentOffset = myInstance . getPosition ( ) ;
*
* @ method getPosition
* @ return { Number } The position of the playhead in the sound , in milliseconds .
* /
p . getPosition = function ( ) {
2014-02-02 19:31:06 -05:00
if ( this . _paused || this . sourceNode == null ) {
var pos = this . _offset ;
2014-01-03 13:32:13 -05:00
} else {
2014-11-18 18:26:26 -05:00
var pos = this . _owner . context . currentTime - this . _playbackStartTime ;
2014-01-03 13:32:13 -05:00
}
return pos * 1000 ; // pos in seconds * 1000 to give milliseconds
} ;
/ * *
2014-02-02 19:31:06 -05:00
* Set the position of the playhead in the instance . This can be set while a sound is playing , paused , or
2014-01-03 13:32:13 -05:00
* stopped .
*
* < h4 > Example < / h 4 >
*
* myInstance . setPosition ( myInstance . getDuration ( ) / 2 ) ; // set audio to its halfway point.
*
* @ method setPosition
* @ param { Number } value The position to place the playhead , in milliseconds .
* /
p . setPosition = function ( value ) {
2014-11-18 18:26:26 -05:00
this . _offset = value * 0.001 ; // convert milliseconds to seconds
2014-01-03 13:32:13 -05:00
if ( this . sourceNode && this . playState == createjs . Sound . PLAY _SUCCEEDED ) {
// we need to stop this sound from continuing to play, as we need to recreate the sourceNode to change position
2014-02-02 19:31:06 -05:00
this . sourceNode = this . _cleanUpAudioNode ( this . sourceNode ) ;
this . _sourceNodeNext = this . _cleanUpAudioNode ( this . _sourceNodeNext ) ;
clearTimeout ( this . _soundCompleteTimeout ) ; // clear timeout that triggers sound complete
} // NOTE we cannot just call cleanup because it also calls the Sound function _playFinished which releases this instance in SoundChannel
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
if ( ! this . _paused && this . playState == createjs . Sound . PLAY _SUCCEEDED ) { this . _handleSoundReady ( ) ; }
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Get the duration of the instance , in milliseconds . Note in most cases , you need to play a sound using
2014-02-02 19:31:06 -05:00
* { { # crossLink "SoundInstance/play" } } { { / c r o s s L i n k } } o r t h e S o u n d A P I { { # c r o s s L i n k " S o u n d / p l a y " } } { { / c r o s s L i n k } }
2014-01-03 13:32:13 -05:00
* method before its duration can be reported accurately .
*
* < h4 > Example < / h 4 >
*
* var soundDur = myInstance . getDuration ( ) ;
*
* @ method getDuration
* @ return { Number } The duration of the sound instance in milliseconds .
* /
p . getDuration = function ( ) {
2014-02-02 19:31:06 -05:00
return this . _duration ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
/ * *
* Audio has finished playing . Manually loop it if required .
* @ method _handleSoundComplete
* @ param event
* @ protected
* /
// called internally by _soundCompleteTimeout in WebAudioPlugin
p . _handleSoundComplete = function ( event ) {
this . _offset = 0 ; // have to set this as it can be set by pause during playback
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
if ( this . _remainingLoops != 0 ) {
this . _remainingLoops -- ; // NOTE this introduces a theoretical limit on loops = float max size x 2 - 1
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
// OJR we are using a look ahead approach to ensure smooth looping. We add _sourceNodeNext to the audio
2014-01-03 13:32:13 -05:00
// context so that it starts playing even if this callback is delayed. This technique and the reasons for
// using it are described in greater detail here: http://www.html5rocks.com/en/tutorials/audio/scheduling/
// NOTE the cost of this is that our audio loop may not always match the loop event timing precisely.
2014-02-02 19:31:06 -05:00
if ( this . _sourceNodeNext ) { // this can be set to null, but this should not happen when looping
this . _cleanUpAudioNode ( this . sourceNode ) ;
this . sourceNode = this . _sourceNodeNext ;
2014-11-18 18:26:26 -05:00
this . _playbackStartTime = this . sourceNode . startTime ;
this . _sourceNodeNext = this . _createAndPlayAudioNode ( this . _playbackStartTime , 0 ) ;
2014-02-02 19:31:06 -05:00
this . _soundCompleteTimeout = setTimeout ( this . _endedHandler , this . _duration ) ;
2014-01-03 13:32:13 -05:00
}
else {
2014-11-18 18:26:26 -05:00
this . _handleSoundReady ( ) ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
this . _sendEvent ( "loop" ) ;
2014-01-03 13:32:13 -05:00
return ;
}
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _FINISHED ;
2014-02-02 19:31:06 -05:00
this . _sendEvent ( "complete" ) ;
2014-01-03 13:32:13 -05:00
} ;
// Play has failed, which can happen for a variety of reasons.
p . playFailed = function ( ) {
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _FAILED ;
2014-02-02 19:31:06 -05:00
this . _sendEvent ( "failed" ) ;
2014-01-03 13:32:13 -05:00
} ;
p . toString = function ( ) {
return "[WebAudioPlugin SoundInstance]" ;
} ;
createjs . WebAudioPlugin . SoundInstance = SoundInstance ;
} ( ) ) ;
( function ( ) {
"use strict" ;
/ * *
* An internal helper class that preloads web audio via XHR . Note that this class and its methods are not documented
* properly to avoid generating HTML documentation .
* # class Loader
* @ param { String } src The source of the sound to load .
* @ param { Object } owner A reference to the class that created this instance .
* @ constructor
* /
function Loader ( src , owner ) {
2014-02-02 19:31:06 -05:00
this . _init ( src , owner ) ;
2014-01-03 13:32:13 -05:00
}
var p = Loader . prototype ;
2014-11-18 18:26:26 -05:00
p . constructor = Loader ;
2014-01-03 13:32:13 -05:00
// the request object for or XHR2 request
p . request = null ;
p . owner = null ;
p . progress = - 1 ;
/ * *
* The source of the sound to load . Used by callback functions when we return this class .
* # property src
* @ type { String }
* /
p . src = null ;
/ * *
* The decoded AudioBuffer array that is returned when loading is complete .
* # property result
* @ type { AudioBuffer }
* @ protected
* /
p . result = null ;
// Calbacks
/ * *
* The callback that fires when the load completes . This follows HTML tag naming .
* # property onload
* @ type { Method }
* /
p . onload = null ;
/ * *
* The callback that fires as the load progresses . This follows HTML tag naming .
* # property onprogress
* @ type { Method }
* /
p . onprogress = null ;
/ * *
2014-11-18 18:26:26 -05:00
* The callback that fires if the load hits an error . This follows HTML tag naming .
* # property onerror
2014-01-03 13:32:13 -05:00
* @ type { Method }
* @ protected
* /
2014-11-18 18:26:26 -05:00
p . onerror = null ;
2014-01-03 13:32:13 -05:00
// constructor
2014-02-02 19:31:06 -05:00
p . _init = function ( src , owner ) {
2014-01-03 13:32:13 -05:00
this . src = src ;
this . owner = owner ;
} ;
/ * *
* Begin loading the content .
* # method load
* @ param { String } src The path to the sound .
* /
p . load = function ( src ) {
2014-11-18 18:26:26 -05:00
if ( src != null ) { this . src = src ; }
2014-01-03 13:32:13 -05:00
this . request = new XMLHttpRequest ( ) ;
this . request . open ( "GET" , this . src , true ) ;
this . request . responseType = "arraybuffer" ;
this . request . onload = createjs . proxy ( this . handleLoad , this ) ;
2014-11-18 18:26:26 -05:00
this . request . onerror = createjs . proxy ( this . handleError , this ) ;
2014-01-03 13:32:13 -05:00
this . request . onprogress = createjs . proxy ( this . handleProgress , this ) ;
this . request . send ( ) ;
} ;
/ * *
* The loader has reported progress .
*
* < strong > Note < / s t r o n g > : t h i s i s n o t a p u b l i c A P I , b u t i s u s e d t o a l l o w p r e l o a d e r s t o s u b s c r i b e t o l o a d
* progress as if this is an HTML audio tag . This reason is why this still uses a callback instead of an event .
* # method handleProgress
2014-11-18 18:26:26 -05:00
* @ param { event } event Progress event that gives event . loaded and event . total if server is configured correctly
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-11-18 18:26:26 -05:00
p . handleProgress = function ( event ) {
if ( ! event || event . loaded > 0 && event . total == 0 ) {
return ; // Sometimes we get no "total", so just ignore the progress event.
}
this . progress = event . loaded / event . total ;
this . onprogress && this . onprogress ( { loaded : event . loaded , total : event . total , progress : this . progress } ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* The sound has completed loading .
* # method handleLoad
* @ protected
* /
p . handleLoad = function ( ) {
this . owner . context . decodeAudioData ( this . request . response ,
createjs . proxy ( this . handleAudioDecoded , this ) ,
createjs . proxy ( this . handleError , this ) ) ;
} ;
/ * *
* The audio has been decoded .
* # method handleAudioDecoded
* @ protected
* /
p . handleAudioDecoded = function ( decodedAudio ) {
this . progress = 1 ;
this . result = decodedAudio ;
this . owner . addPreloadResults ( this . src , this . result ) ;
2014-11-18 18:26:26 -05:00
this . onload && this . onload ( this ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Errors have been caused by the loader .
* # method handleError
* @ protected
* /
p . handleError = function ( evt ) {
this . owner . removeSound ( this . src ) ;
this . onerror && this . onerror ( evt ) ;
} ;
2014-11-18 18:26:26 -05:00
/ * *
* Remove all external references from loader
* # method cleanUp
* /
p . cleanUp = function ( ) {
if ( ! this . request ) { return ; }
this . src = null ;
this . owner = null ;
this . request . onload = null ;
this . request . onerror = null ;
this . request . onprogress = null ;
this . request = null ;
this . onload = null ;
this . onprogress = null ;
this . onerror = null ;
} ;
2014-01-03 13:32:13 -05:00
p . toString = function ( ) {
return "[WebAudioPlugin Loader]" ;
} ;
createjs . WebAudioPlugin . Loader = Loader ;
} ( ) ) ;
/ *
* HTMLAudioPlugin
* 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" ;
/ * *
* Play sounds using HTML & lt ; audio & gt ; tags in the browser . This plugin is the second priority plugin installed
* by default , after the { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } . F o r o l d e r b r o w s e r s t h a t d o n o t s u p p o r t h t m l
* audio , include and install the { { # crossLink "FlashPlugin" } } { { / c r o s s L i n k } } .
*
* < h4 > Known Browser and OS issues for HTML Audio < / h 4 >
* < b > All browsers < /b><br / >
* Testing has shown in all browsers there is a limit to how many audio tag instances you are allowed . If you exceed
* this limit , you can expect to see unpredictable results . This will be seen as soon as you register sounds , as
2014-02-02 19:31:06 -05:00
* tags are precreated to allow Chrome to load them . Please use { { # crossLink "Sound.MAX_INSTANCES" } } { { / c r o s s L i n k } } a s
2014-01-03 13:32:13 -05:00
* a guide to how many total audio tags you can safely use in all browsers .
*
2014-11-18 18:26:26 -05:00
* < b > IE html limitations < /b><br / >
2014-01-03 13:32:13 -05:00
* < ul > < li > There is a delay in applying volume changes to tags that occurs once playback is started . So if you have
* muted all sounds , they will all play during this delay until the mute applies internally . This happens regardless of
* when or how you apply the volume change , as the tag seems to need to play to apply it . < / l i >
* < li > MP3 encoding will not always work for audio tags if it 's not default. We' ve found default encoding with
* 64 kbps works . < / l i >
2014-11-18 18:26:26 -05:00
* < li > Occasionally very short samples will get cut off . < / l i >
2014-01-03 13:32:13 -05:00
* < li > There is a limit to how many audio tags you can load and play at once , which appears to be determined by
2014-11-18 18:26:26 -05:00
* hardware and browser settings . See { { # crossLink "HTMLAudioPlugin.MAX_INSTANCES" } } { { / c r o s s L i n k } } f o r a s a f e e s t i m a t e .
* Note that audio sprites can be used as a solution to this issue . < / l i > < / u l >
2014-01-03 13:32:13 -05:00
*
* < b > Safari limitations < /b><br / >
* < ul > < li > Safari requires Quicktime to be installed for audio playback . < / l i > < / u l >
*
* < b > iOS 6 limitations < /b><br / >
2014-02-02 19:31:06 -05:00
* < ul > < li > Note it is recommended to use { { # crossLink "WebAudioPlugin" } } { { / c r o s s L i n k } } f o r i O S ( 6 + ) < / l i >
* < li > HTML Audio is disabled by default because < / l i >
* < li > can only have one & lt ; audio & gt ; tag < / l i >
* < li > can not preload or autoplay the audio < / l i >
* < li > can not cache the audio < / l i >
* < li > can not play the audio except inside a user initiated event . < / l i >
2014-11-18 18:26:26 -05:00
* < li > audio sprites can be used to mitigate some of these issues and are strongly recommended on iOS < / l i >
2014-02-02 19:31:06 -05:00
* < / u l >
*
* < b > Android Native Browser limitations < /b><br / >
2014-01-03 13:32:13 -05:00
* < ul > < li > We have no control over audio volume . Only the user can set volume on their device . < / l i >
2014-02-02 19:31:06 -05:00
* < li > We can only play audio inside a user event ( touch / click ) . This currently means you cannot loop sound or use a delay . < / l i > < / u l >
2014-01-03 13:32:13 -05:00
* < b > Android Chrome 26.0 . 1410.58 specific limitations < /b><br / >
2014-02-02 19:31:06 -05:00
* < ul > < li > Chrome reports true when you run createjs . Sound . BrowserDetect . isChrome , but is a different browser
2014-01-03 13:32:13 -05:00
* with different abilities . < / l i >
* < li > Can only play 1 sound at a time . < / l i >
* < li > Sound is not cached . < / l i >
* < li > Sound can only be loaded in a user initiated touch / click event . < / l i >
* < li > There is a delay before a sound is played , presumably while the src is loaded . < / l i >
* < / u l >
*
* See { { # crossLink "Sound" } } { { / c r o s s L i n k } } f o r g e n e r a l n o t e s o n k n o w n i s s u e s .
*
* @ class HTMLAudioPlugin
* @ constructor
* /
function HTMLAudioPlugin ( ) {
2014-02-02 19:31:06 -05:00
this . _init ( ) ;
2014-01-03 13:32:13 -05:00
}
var s = HTMLAudioPlugin ;
/ * *
* The maximum number of instances that can be loaded and played . This is a browser limitation , primarily limited to IE9 .
* The actual number varies from browser to browser ( and is largely hardware dependant ) , but this is a safe estimate .
* @ property MAX _INSTANCES
* @ type { Number }
* @ default 30
* @ static
* /
s . MAX _INSTANCES = 30 ;
/ * *
* Event constant for the "canPlayThrough" event for cleaner code .
2014-02-02 19:31:06 -05:00
* @ property _AUDIO _READY
2014-01-03 13:32:13 -05:00
* @ type { String }
* @ default canplaythrough
* @ static
2014-02-02 19:31:06 -05:00
* @ protected
2014-01-03 13:32:13 -05:00
* /
2014-02-02 19:31:06 -05:00
s . _AUDIO _READY = "canplaythrough" ;
2014-01-03 13:32:13 -05:00
/ * *
* Event constant for the "ended" event for cleaner code .
2014-02-02 19:31:06 -05:00
* @ property _AUDIO _ENDED
2014-01-03 13:32:13 -05:00
* @ type { String }
* @ default ended
* @ static
2014-02-02 19:31:06 -05:00
* @ protected
2014-01-03 13:32:13 -05:00
* /
2014-02-02 19:31:06 -05:00
s . _AUDIO _ENDED = "ended" ;
2014-01-03 13:32:13 -05:00
/ * *
* Event constant for the "seeked" event for cleaner code . We utilize this event for maintaining loop events .
2014-02-02 19:31:06 -05:00
* @ property _AUDIO _SEEKED
2014-01-03 13:32:13 -05:00
* @ type { String }
* @ default seeked
* @ static
2014-02-02 19:31:06 -05:00
* @ protected
2014-01-03 13:32:13 -05:00
* /
2014-02-02 19:31:06 -05:00
s . _AUDIO _SEEKED = "seeked" ;
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* Event constant for the "stalled" event for cleaner code .
* @ property _AUDIO _STALLED
2014-01-03 13:32:13 -05:00
* @ type { String }
2014-02-02 19:31:06 -05:00
* @ default stalled
2014-01-03 13:32:13 -05:00
* @ static
2014-02-02 19:31:06 -05:00
* @ protected
2014-01-03 13:32:13 -05:00
* /
2014-02-02 19:31:06 -05:00
s . _AUDIO _STALLED = "stalled" ;
2014-01-03 13:32:13 -05:00
2014-11-18 18:26:26 -05:00
/ * *
* Event constant for the "timeupdate" event for cleaner code . Utilized for looping audio sprites .
* This event callsback ever 15 to 250 ms and can be dropped by the browser for performance .
* @ property _TIME _UPDATE
* @ type { String }
* @ default timeupdate
* @ static
* @ protected
* /
s . _TIME _UPDATE = "timeupdate" ;
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* The capabilities of the plugin . This is generated via the the SoundInstance { { # crossLink "HTMLAudioPlugin/_generateCapabilities" } } { { / c r o s s L i n k } }
* method . Please see the Sound { { # crossLink "Sound/getCapabilities" } } { { / c r o s s L i n k } } m e t h o d f o r a n o v e r v i e w o f a l l
* of the available properties .
* @ property _capabilities
* @ type { Object }
* @ protected
2014-01-03 13:32:13 -05:00
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _capabilities = null ;
2014-01-03 13:32:13 -05:00
/ * *
2014-11-18 18:26:26 -05:00
* Deprecated now that we have audio sprite support . Audio sprites are strongly recommend on iOS .
2014-01-03 13:32:13 -05:00
* < li > it can only have one & lt ; audio & gt ; tag < / l i >
* < li > can not preload or autoplay the audio < / l i >
* < li > can not cache the audio < / l i >
* < li > can not play the audio except inside a user initiated event < / l i >
*
* @ property enableIOS
* @ type { Boolean }
* @ default false
2014-11-18 18:26:26 -05:00
* @ deprecated
2014-01-03 13:32:13 -05:00
* /
s . enableIOS = false ;
/ * *
* Determine if the plugin can be used in the current browser / OS . Note that HTML audio is available in most modern
* browsers , but is disabled in iOS because of its limitations .
* @ method isSupported
* @ return { Boolean } If the plugin can be initialized .
* @ static
* /
s . isSupported = function ( ) {
2014-02-02 19:31:06 -05:00
s . _generateCapabilities ( ) ;
2014-11-18 18:26:26 -05:00
if ( s . _capabilities == null ) { return false ; }
2014-01-03 13:32:13 -05:00
return true ;
} ;
/ * *
* Determine the capabilities of the plugin . Used internally . Please see the Sound API { { # crossLink "Sound/getCapabilities" } } { { / c r o s s L i n k } }
* method for an overview of plugin capabilities .
2014-02-02 19:31:06 -05:00
* @ method _generateCapabilities
2014-01-03 13:32:13 -05:00
* @ static
* @ protected
* /
2014-02-02 19:31:06 -05:00
s . _generateCapabilities = function ( ) {
2014-11-18 18:26:26 -05:00
if ( s . _capabilities != null ) { return ; }
var t = document . createElement ( "audio" ) ;
if ( t . canPlayType == null ) { return null ; }
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
s . _capabilities = {
2014-01-03 13:32:13 -05:00
panning : true ,
volume : true ,
tracks : - 1
} ;
// determine which extensions our browser supports for this plugin by iterating through Sound.SUPPORTED_EXTENSIONS
var supportedExtensions = createjs . Sound . SUPPORTED _EXTENSIONS ;
var extensionMap = createjs . Sound . EXTENSION _MAP ;
for ( var i = 0 , l = supportedExtensions . length ; i < l ; i ++ ) {
var ext = supportedExtensions [ i ] ;
var playType = extensionMap [ ext ] || ext ;
2014-02-02 19:31:06 -05:00
s . _capabilities [ ext ] = ( t . canPlayType ( "audio/" + ext ) != "no" && t . canPlayType ( "audio/" + ext ) != "" ) || ( t . canPlayType ( "audio/" + playType ) != "no" && t . canPlayType ( "audio/" + playType ) != "" ) ;
2014-01-03 13:32:13 -05:00
} // OJR another way to do this might be canPlayType:"m4a", codex: mp4
}
var p = HTMLAudioPlugin . prototype ;
2014-11-18 18:26:26 -05:00
p . constructor = HTMLAudioPlugin ;
2014-01-03 13:32:13 -05:00
// doc'd above
2014-02-02 19:31:06 -05:00
p . _capabilities = null ;
2014-01-03 13:32:13 -05:00
/ * *
* Object hash indexed by the source of each file to indicate if an audio source is loaded , or loading .
2014-02-02 19:31:06 -05:00
* @ property _audioSources
2014-01-03 13:32:13 -05:00
* @ type { Object }
* @ protected
* @ since 0.4 . 0
* /
2014-02-02 19:31:06 -05:00
p . _audioSources = null ;
2014-01-03 13:32:13 -05:00
/ * *
2014-11-18 18:26:26 -05:00
* The default number of instances to allow . Used by { { # crossLink "Sound" } } { { / c r o s s L i n k } } w h e n a s o u r c e
2014-01-03 13:32:13 -05:00
* is registered using the { { # crossLink "Sound/register" } } { { / c r o s s L i n k } } m e t h o d . T h i s i s o n l y u s e d i f
* a value is not provided .
*
* < b > NOTE this property only exists as a limitation of HTML audio . < / b >
* @ property defaultNumChannels
* @ type { Number }
* @ default 2
* @ since 0.4 . 0
* /
p . defaultNumChannels = 2 ;
/ * *
* An initialization function run by the constructor
2014-02-02 19:31:06 -05:00
* @ method _init
2014-01-03 13:32:13 -05:00
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _init = function ( ) {
this . _capabilities = s . _capabilities ;
this . _audioSources = { } ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Pre - register a sound instance when preloading / setup . This is called by { { # crossLink "Sound" } } { { / c r o s s L i n k } } .
* Note that this provides an object containing a tag used for preloading purposes , which
2014-02-02 19:31:06 -05:00
* < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > c a n u s e t o a s s i s t w i t h p r e l o a d i n g .
2014-01-03 13:32:13 -05:00
* @ method register
* @ param { String } src The source of the audio
* @ param { Number } instances The number of concurrently playing instances to allow for the channel at any time .
* @ return { Object } A result object , containing a tag for preloading purposes and a numChannels value for internally
* controlling how many instances of a source can be played by default .
* /
p . register = function ( src , instances ) {
2014-02-02 19:31:06 -05:00
this . _audioSources [ src ] = true ; // Note this does not mean preloading has started
2014-01-03 13:32:13 -05:00
var channel = createjs . HTMLAudioPlugin . TagPool . get ( src ) ;
var tag = null ;
2014-11-18 18:26:26 -05:00
var l = instances ;
for ( var i = 0 ; i < l ; i ++ ) {
2014-02-02 19:31:06 -05:00
tag = this . _createTag ( src ) ;
2014-01-03 13:32:13 -05:00
channel . add ( tag ) ;
}
return {
2014-11-18 18:26:26 -05:00
tag : tag // Return one instance for preloading purposes
2014-01-03 13:32:13 -05:00
} ;
} ;
/ * *
* Create an HTML audio tag .
2014-02-02 19:31:06 -05:00
* @ method _createTag
2014-01-03 13:32:13 -05:00
* @ param { String } src The source file to set for the audio tag .
* @ return { HTMLElement } Returns an HTML audio tag .
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _createTag = function ( src ) {
2014-01-03 13:32:13 -05:00
var tag = document . createElement ( "audio" ) ;
tag . autoplay = false ;
tag . preload = "none" ;
//LM: Firefox fails when this the preload="none" for other tags, but it needs to be "none" to ensure PreloadJS works.
tag . src = src ;
return tag ;
} ;
/ * *
* Remove a sound added using { { # crossLink "HTMLAudioPlugin/register" } } { { / c r o s s L i n k } } . N o t e t h i s d o e s n o t c a n c e l
* a preload .
* @ method removeSound
* @ param { String } src The sound URI to unload .
* @ since 0.4 . 1
* /
p . removeSound = function ( src ) {
2014-02-02 19:31:06 -05:00
delete ( this . _audioSources [ src ] ) ;
2014-01-03 13:32:13 -05:00
createjs . HTMLAudioPlugin . TagPool . remove ( src ) ;
} ;
/ * *
* Remove all sounds added using { { # crossLink "HTMLAudioPlugin/register" } } { { / c r o s s L i n k } } . N o t e t h i s d o e s n o t c a n c e l a p r e l o a d .
* @ method removeAllSounds
* @ param { String } src The sound URI to unload .
* @ since 0.4 . 1
* /
p . removeAllSounds = function ( ) {
2014-11-18 18:26:26 -05:00
this . _audioSources = { } ;
2014-01-03 13:32:13 -05:00
createjs . HTMLAudioPlugin . TagPool . removeAll ( ) ;
} ;
/ * *
* Create a sound instance . If the sound has not been preloaded , it is internally preloaded here .
* @ method create
* @ param { String } src The sound source to use .
2014-11-18 18:26:26 -05:00
* @ 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 .
2014-01-03 13:32:13 -05:00
* @ return { SoundInstance } A sound instance for playback and control .
* /
2014-11-18 18:26:26 -05:00
p . create = function ( src , startTime , duration ) {
2014-01-03 13:32:13 -05:00
// if this sound has not be registered, create a tag and preload it
if ( ! this . isPreloadStarted ( src ) ) {
var channel = createjs . HTMLAudioPlugin . TagPool . get ( src ) ;
2014-02-02 19:31:06 -05:00
var tag = this . _createTag ( src ) ;
2014-01-03 13:32:13 -05:00
tag . id = src ;
channel . add ( tag ) ;
this . preload ( src , { tag : tag } ) ;
}
2014-11-18 18:26:26 -05:00
return new createjs . HTMLAudioPlugin . SoundInstance ( src , startTime , duration , this ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Checks if preloading has started for a specific source .
* @ method isPreloadStarted
* @ param { String } src The sound URI to check .
* @ return { Boolean } If the preload has started .
* @ since 0.4 . 0
* /
p . isPreloadStarted = function ( src ) {
2014-02-02 19:31:06 -05:00
return ( this . _audioSources [ src ] != null ) ;
2014-01-03 13:32:13 -05:00
} ;
/ * *
* Internally preload a sound .
* @ method preload
* @ param { String } src The sound URI to load .
2014-11-18 18:26:26 -05:00
* @ param { Object } tag An HTML audio tag used to load src .
2014-01-03 13:32:13 -05:00
* @ since 0.4 . 0
* /
2014-11-18 18:26:26 -05:00
p . preload = function ( src , tag ) {
2014-02-02 19:31:06 -05:00
this . _audioSources [ src ] = true ;
2014-11-18 18:26:26 -05:00
new createjs . HTMLAudioPlugin . Loader ( src , tag ) ;
2014-01-03 13:32:13 -05:00
} ;
p . toString = function ( ) {
return "[HTMLAudioPlugin]" ;
} ;
createjs . HTMLAudioPlugin = HTMLAudioPlugin ;
} ( ) ) ;
( function ( ) {
"use strict" ;
// NOTE Documentation for the SoundInstance class in WebAudioPlugin file. Each plugin generates a SoundInstance that
// follows the same interface.
2014-11-18 18:26:26 -05:00
function SoundInstance ( src , startTime , duration , owner ) {
this . _init ( src , startTime , duration , owner ) ;
2014-01-03 13:32:13 -05:00
}
var p = SoundInstance . prototype = new createjs . EventDispatcher ( ) ;
2014-11-18 18:26:26 -05:00
p . constructor = SoundInstance ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
p . src = null ;
2014-01-03 13:32:13 -05:00
p . uniqueId = - 1 ;
p . playState = null ;
2014-02-02 19:31:06 -05:00
p . _owner = null ;
2014-01-03 13:32:13 -05:00
p . loaded = false ;
2014-02-02 19:31:06 -05:00
p . _offset = 0 ;
2014-11-18 18:26:26 -05:00
p . _startTime = 0 ;
2014-01-03 13:32:13 -05:00
p . _volume = 1 ;
2014-11-18 18:26:26 -05:00
if ( createjs . definePropertySupported ) {
2014-01-03 13:32:13 -05:00
Object . defineProperty ( p , "volume" , {
get : function ( ) {
return this . _volume ;
} ,
set : function ( value ) {
if ( Number ( value ) == null ) { return ; }
value = Math . max ( 0 , Math . min ( 1 , value ) ) ;
this . _volume = value ;
2014-02-02 19:31:06 -05:00
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
}
} ) ;
2014-11-18 18:26:26 -05:00
}
2014-01-03 13:32:13 -05:00
p . pan = 0 ;
2014-02-02 19:31:06 -05:00
p . _duration = 0 ;
2014-11-18 18:26:26 -05:00
p . _audioSpriteStopTime = null ; // HTMLAudioPlugin only
2014-02-02 19:31:06 -05:00
p . _remainingLoops = 0 ;
2014-11-18 18:26:26 -05:00
if ( createjs . definePropertySupported ) {
Object . defineProperty ( p , "loop" , {
get : function ( ) {
return this . _remainingLoops ;
} ,
set : function ( value ) {
if ( this . tag != null ) {
// remove looping
if ( this . _remainingLoops != 0 && value == 0 ) {
this . tag . loop = false ;
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
}
// add looping
if ( this . _remainingLoops == 0 && value != 0 ) {
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
this . tag . loop = true ;
}
}
this . _remainingLoops = value ;
}
} ) ;
}
2014-02-02 19:31:06 -05:00
p . _delayTimeoutId = null ;
2014-01-03 13:32:13 -05:00
p . tag = null ;
2014-02-02 19:31:06 -05:00
p . _muted = false ;
2014-01-03 13:32:13 -05:00
p . paused = false ;
2014-02-02 19:31:06 -05:00
p . _paused = false ;
2014-01-03 13:32:13 -05:00
// Proxies, make removing listeners easier.
2014-02-02 19:31:06 -05:00
p . _endedHandler = null ;
p . _readyHandler = null ;
p . _stalledHandler = null ;
2014-11-18 18:26:26 -05:00
p . _audioSpriteEndHandler = null ;
2014-01-03 13:32:13 -05:00
p . loopHandler = null ;
// Constructor
2014-11-18 18:26:26 -05:00
p . _init = function ( src , startTime , duration , owner ) {
2014-01-03 13:32:13 -05:00
this . src = src ;
2014-11-18 18:26:26 -05:00
this . _startTime = startTime || 0 ; // convert ms to s as web audio handles everything in seconds
if ( duration ) {
this . _duration = duration ;
this . _audioSpriteStopTime = ( startTime + duration ) * 0.001 ;
} else {
this . _duration = createjs . HTMLAudioPlugin . TagPool . getDuration ( this . src ) ;
}
2014-02-02 19:31:06 -05:00
this . _owner = owner ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . _endedHandler = createjs . proxy ( this . _handleSoundComplete , this ) ;
this . _readyHandler = createjs . proxy ( this . _handleSoundReady , this ) ;
this . _stalledHandler = createjs . proxy ( this . _handleSoundStalled , this ) ;
2014-11-18 18:26:26 -05:00
this . _ _audioSpriteEndHandler = createjs . proxy ( this . _handleAudioSpriteLoop , this ) ;
2014-01-03 13:32:13 -05:00
this . loopHandler = createjs . proxy ( this . handleSoundLoop , this ) ;
} ;
2014-02-02 19:31:06 -05:00
p . _sendEvent = function ( type ) {
2014-01-03 13:32:13 -05:00
var event = new createjs . Event ( type ) ;
this . dispatchEvent ( event ) ;
} ;
2014-02-02 19:31:06 -05:00
p . _cleanUp = function ( ) {
2014-01-03 13:32:13 -05:00
var tag = this . tag ;
if ( tag != null ) {
tag . pause ( ) ;
2014-11-18 18:26:26 -05:00
this . tag . loop = false ;
2014-02-02 19:31:06 -05:00
tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _ENDED , this . _endedHandler , false ) ;
tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _READY , this . _readyHandler , false ) ;
tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
2014-11-18 18:26:26 -05:00
tag . removeEventListener ( createjs . HTMLAudioPlugin . _TIME _UPDATE , this . _ _audioSpriteEndHandler , false ) ;
2014-01-03 13:32:13 -05:00
try {
2014-11-18 18:26:26 -05:00
tag . currentTime = this . _startTime ;
2014-01-03 13:32:13 -05:00
} catch ( e ) {
} // Reset Position
createjs . HTMLAudioPlugin . TagPool . setInstance ( this . src , tag ) ;
this . tag = null ;
}
2014-02-02 19:31:06 -05:00
clearTimeout ( this . _delayTimeoutId ) ;
createjs . Sound . _playFinished ( this ) ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
p . _interrupt = function ( ) {
2014-11-18 18:26:26 -05:00
if ( this . tag == null ) { return ; }
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _INTERRUPTED ;
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
this . paused = this . _paused = false ;
this . _sendEvent ( "interrupted" ) ;
2014-01-03 13:32:13 -05:00
} ;
// Public API
p . play = function ( interrupt , delay , offset , loop , volume , pan ) {
2014-11-18 18:26:26 -05:00
if ( this . playState == createjs . Sound . PLAY _SUCCEEDED ) {
if ( interrupt instanceof Object ) {
offset = interrupt . offset ;
loop = interrupt . loop ;
volume = interrupt . volume ;
pan = interrupt . pan ;
}
if ( offset != null ) { this . setPosition ( offset ) }
if ( loop != null ) { this . loop = loop ; }
if ( volume != null ) { this . setVolume ( volume ) ; }
if ( pan != null ) { this . setPan ( pan ) ; }
if ( this . _paused ) { this . resume ( ) ; }
return ;
}
this . _cleanUp ( ) ;
2014-02-02 19:31:06 -05:00
createjs . Sound . _playInstance ( this , interrupt , delay , offset , loop , volume , pan ) ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
p . _beginPlaying = function ( offset , loop , volume , pan ) {
2014-01-03 13:32:13 -05:00
var tag = this . tag = createjs . HTMLAudioPlugin . TagPool . getInstance ( this . src ) ;
if ( tag == null ) {
this . playFailed ( ) ;
return - 1 ;
}
2014-02-02 19:31:06 -05:00
tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _ENDED , this . _endedHandler , false ) ;
2014-01-03 13:32:13 -05:00
// Reset this instance.
2014-02-02 19:31:06 -05:00
this . _offset = offset ;
2014-01-03 13:32:13 -05:00
this . volume = volume ;
2014-11-18 18:26:26 -05:00
this . _updateVolume ( ) ;
2014-02-02 19:31:06 -05:00
this . _remainingLoops = loop ;
2014-01-03 13:32:13 -05:00
if ( tag . readyState !== 4 ) {
2014-02-02 19:31:06 -05:00
tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _READY , this . _readyHandler , false ) ;
tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _STALLED , this . _stalledHandler , false ) ;
2014-01-03 13:32:13 -05:00
tag . preload = "auto" ; // This is necessary for Firefox, as it won't ever "load" until this is set.
tag . load ( ) ;
} else {
2014-02-02 19:31:06 -05:00
this . _handleSoundReady ( null ) ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
this . _sendEvent ( "succeeded" ) ;
2014-01-03 13:32:13 -05:00
return 1 ;
} ;
// Note: Sounds stall when trying to begin playback of a new audio instance when the existing instances
// has not loaded yet. This doesn't mean the sound will not play.
2014-02-02 19:31:06 -05:00
p . _handleSoundStalled = function ( event ) {
2014-11-18 18:26:26 -05:00
this . _cleanUp ( ) ; // OJR this will stop playback, we could remove this and let the developer decide how to handle stalled instances
2014-02-02 19:31:06 -05:00
this . _sendEvent ( "failed" ) ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
p . _handleSoundReady = function ( event ) {
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _SUCCEEDED ;
2014-02-02 19:31:06 -05:00
this . paused = this . _paused = false ;
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _READY , this . _readyHandler , false ) ;
2014-11-18 18:26:26 -05:00
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
if ( this . _offset >= this . getDuration ( ) ) {
2014-11-18 18:26:26 -05:00
this . playFailed ( ) ;
2014-01-03 13:32:13 -05:00
return ;
}
2014-11-18 18:26:26 -05:00
this . tag . currentTime = ( this . _startTime + this . _offset ) * 0.001 ;
if ( this . _audioSpriteStopTime ) {
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _ENDED , this . _endedHandler , false ) ;
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _TIME _UPDATE , this . _ _audioSpriteEndHandler , false ) ;
} else {
if ( this . _remainingLoops != 0 ) {
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
this . tag . loop = true ;
}
2014-01-03 13:32:13 -05:00
}
2014-11-18 18:26:26 -05:00
2014-01-03 13:32:13 -05:00
this . tag . play ( ) ;
} ;
p . pause = function ( ) {
2014-02-02 19:31:06 -05:00
if ( ! this . _paused && this . playState == createjs . Sound . PLAY _SUCCEEDED && this . tag != null ) {
this . paused = this . _paused = true ;
2014-01-03 13:32:13 -05:00
this . tag . pause ( ) ;
2014-02-02 19:31:06 -05:00
clearTimeout ( this . _delayTimeoutId ) ;
2014-01-03 13:32:13 -05:00
return true ;
}
return false ;
} ;
p . resume = function ( ) {
2014-11-18 18:26:26 -05:00
if ( ! this . _paused || this . tag == null ) { return false ; }
2014-02-02 19:31:06 -05:00
this . paused = this . _paused = false ;
2014-01-03 13:32:13 -05:00
this . tag . play ( ) ;
return true ;
} ;
p . stop = function ( ) {
2014-02-02 19:31:06 -05:00
this . _offset = 0 ;
2014-01-03 13:32:13 -05:00
this . pause ( ) ;
this . playState = createjs . Sound . PLAY _FINISHED ;
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
p . setMasterVolume = function ( value ) {
2014-02-02 19:31:06 -05:00
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
} ;
p . setVolume = function ( value ) {
this . volume = value ;
return true ;
} ;
2014-02-02 19:31:06 -05:00
p . _updateVolume = function ( ) {
2014-01-03 13:32:13 -05:00
if ( this . tag != null ) {
2014-02-02 19:31:06 -05:00
var newVolume = ( this . _muted || createjs . Sound . _masterMute ) ? 0 : this . _volume * createjs . Sound . _masterVolume ;
2014-11-18 18:26:26 -05:00
if ( newVolume != this . tag . volume ) { this . tag . volume = newVolume ; }
2014-01-03 13:32:13 -05:00
}
} ;
p . getVolume = function ( value ) {
return this . volume ;
} ;
p . setMasterMute = function ( isMuted ) {
2014-02-02 19:31:06 -05:00
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
} ;
p . setMute = function ( isMuted ) {
2014-11-18 18:26:26 -05:00
if ( isMuted == null ) { return false ; }
2014-02-02 19:31:06 -05:00
this . _muted = isMuted ;
this . _updateVolume ( ) ;
2014-01-03 13:32:13 -05:00
return true ;
} ;
p . getMute = function ( ) {
2014-02-02 19:31:06 -05:00
return this . _muted ;
2014-01-03 13:32:13 -05:00
} ;
2014-11-18 18:26:26 -05:00
// Can not set pan in HTML audio
2014-01-03 13:32:13 -05:00
p . setPan = function ( value ) {
return false ;
} ;
p . getPan = function ( ) {
return 0 ;
} ;
p . getPosition = function ( ) {
2014-11-18 18:26:26 -05:00
if ( this . tag == null ) { return this . _offset ; }
return ( this . tag . currentTime * 1000 ) - this . _startTime ;
2014-01-03 13:32:13 -05:00
} ;
p . setPosition = function ( value ) {
if ( this . tag == null ) {
2014-02-02 19:31:06 -05:00
this . _offset = value
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
2014-11-18 18:26:26 -05:00
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . _handleSetPositionSeek , false ) ;
2014-01-03 13:32:13 -05:00
try {
2014-11-18 18:26:26 -05:00
value = value + this . _startTime ;
2014-01-03 13:32:13 -05:00
this . tag . currentTime = value * 0.001 ;
} catch ( error ) { // Out of range
2014-11-18 18:26:26 -05:00
this . _handleSetPositionSeek ( null ) ;
2014-01-03 13:32:13 -05:00
return false ;
}
}
return true ;
} ;
2014-11-18 18:26:26 -05:00
p . _handleSetPositionSeek = function ( event ) {
if ( this . tag == null ) { return ; }
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . _handleSetPositionSeek , false ) ;
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
} ;
p . getDuration = function ( ) { // NOTE this will always return 0 until sound has been played unless it is set
2014-02-02 19:31:06 -05:00
return this . _duration ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
p . _handleSoundComplete = function ( event ) {
this . _offset = 0 ;
2014-01-03 13:32:13 -05:00
this . playState = createjs . Sound . PLAY _FINISHED ;
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
this . _sendEvent ( "complete" ) ;
2014-01-03 13:32:13 -05:00
} ;
2014-11-18 18:26:26 -05:00
// NOTE because of the inaccuracies in the timeupdate event (15 - 250ms) and in setting the tag to the desired timed
// (up to 300ms), it is strongly recommended not to loop audio sprites with HTML Audio if smooth looping is desired
p . _handleAudioSpriteLoop = function ( event ) {
if ( this . tag . currentTime <= this . _audioSpriteStopTime ) { return ; }
this . tag . pause ( ) ;
if ( this . _remainingLoops == 0 ) {
this . _handleSoundComplete ( null ) ;
} else {
this . _offset = 0 ;
this . _remainingLoops -- ;
this . tag . currentTime = this . _startTime * 0.001 ;
if ( ! this . _paused ) { this . tag . play ( ) ; }
this . _sendEvent ( "loop" ) ;
}
} ;
2014-01-03 13:32:13 -05:00
// NOTE with this approach audio will loop as reliably as the browser allows
// but we could end up sending the loop event after next loop playback begins
p . handleSoundLoop = function ( event ) {
2014-02-02 19:31:06 -05:00
this . _offset = 0 ;
this . _remainingLoops -- ;
if ( this . _remainingLoops == 0 ) {
2014-01-03 13:32:13 -05:00
this . tag . loop = false ;
2014-02-02 19:31:06 -05:00
this . tag . removeEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
this . _sendEvent ( "loop" ) ;
2014-01-03 13:32:13 -05:00
} ;
p . playFailed = function ( ) {
this . playState = createjs . Sound . PLAY _FAILED ;
2014-02-02 19:31:06 -05:00
this . _cleanUp ( ) ;
this . _sendEvent ( "failed" ) ;
2014-01-03 13:32:13 -05:00
} ;
p . toString = function ( ) {
return "[HTMLAudioPlugin SoundInstance]" ;
} ;
createjs . HTMLAudioPlugin . SoundInstance = SoundInstance ;
} ( ) ) ;
( function ( ) {
"use strict" ;
/ * *
* An internal helper class that preloads html audio via HTMLAudioElement tags . Note that PreloadJS will NOT use
* this load class like it does Flash and WebAudio plugins .
* Note that this class and its methods are not documented properly to avoid generating HTML documentation .
* # class Loader
* @ param { String } src The source of the sound to load .
* @ param { HTMLAudioElement } tag The audio tag of the sound to load .
* @ constructor
* @ protected
* @ since 0.4 . 0
* /
function Loader ( src , tag ) {
2014-02-02 19:31:06 -05:00
this . _init ( src , tag ) ;
} ;
2014-01-03 13:32:13 -05:00
var p = Loader . prototype ;
2014-11-18 18:26:26 -05:00
p . constructor = Loader ;
2014-01-03 13:32:13 -05:00
/ * *
* The source to be loaded .
* # property src
* @ type { String }
* @ default null
* @ protected
* /
p . src = null ;
/ * *
* The tag to load the source with / i n t o .
* # property tag
* @ type { AudioTag }
* @ default null
* @ protected
* /
p . tag = null ;
/ * *
* An interval used to give us progress .
* # property preloadTimer
* @ type { String }
* @ default null
* @ protected
* /
p . preloadTimer = null ;
// Proxies, make removing listeners easier.
p . loadedHandler = null ;
// constructor
2014-02-02 19:31:06 -05:00
p . _init = function ( src , tag ) {
2014-01-03 13:32:13 -05:00
this . src = src ;
this . tag = tag ;
this . preloadTimer = setInterval ( createjs . proxy ( this . preloadTick , this ) , 200 ) ;
// This will tell us when audio is buffered enough to play through, but not when its loaded.
// The tag doesn't keep loading in Chrome once enough has buffered, and we have decided that behaviour is sufficient.
// Note that canplaythrough callback doesn't work in Chrome, we have to use the event.
this . loadedHandler = createjs . proxy ( this . sendLoadedEvent , this ) ; // we need this bind to be able to remove event listeners
this . tag . addEventListener && this . tag . addEventListener ( "canplaythrough" , this . loadedHandler ) ;
if ( this . tag . onreadystatechange == null ) {
2014-11-18 18:26:26 -05:00
this . tag . onreadystatechange = createjs . proxy ( this . sendLoadedEvent , this ) ;
2014-01-03 13:32:13 -05:00
} else {
var f = this . tag . onreadystatechange ;
this . tag . onreadystatechange = function ( ) {
f ( ) ;
2014-11-18 18:26:26 -05:00
this . tag . onreadystatechange = createjs . proxy ( this . sendLoadedEvent , this ) ;
2014-01-03 13:32:13 -05:00
}
}
this . tag . preload = "auto" ;
//this.tag.src = src;
this . tag . load ( ) ;
} ;
/ * *
* Allows us to have preloading progress and tell when its done .
* # method preloadTick
* @ protected
* /
p . preloadTick = function ( ) {
var buffered = this . tag . buffered ;
var duration = this . tag . duration ;
if ( buffered . length > 0 ) {
if ( buffered . end ( 0 ) >= duration - 1 ) {
this . handleTagLoaded ( ) ;
}
}
} ;
/ * *
* Internal handler for when a tag is loaded .
* # method handleTagLoaded
* @ protected
* /
p . handleTagLoaded = function ( ) {
clearInterval ( this . preloadTimer ) ;
} ;
/ * *
* Communicates back to Sound that a load is complete .
* # method sendLoadedEvent
* @ param { Object } evt The load Event
* /
p . sendLoadedEvent = function ( evt ) {
this . tag . removeEventListener && this . tag . removeEventListener ( "canplaythrough" , this . loadedHandler ) ; // cleanup and so we don't send the event more than once
this . tag . onreadystatechange = null ; // cleanup and so we don't send the event more than once
2014-02-02 19:31:06 -05:00
createjs . Sound . _sendFileLoadEvent ( this . src ) ; // fire event or callback on Sound
2014-11-18 18:26:26 -05:00
2014-01-03 13:32:13 -05:00
} ;
// used for debugging
p . toString = function ( ) {
return "[HTMLAudioPlugin Loader]" ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
createjs . HTMLAudioPlugin . Loader = Loader ;
} ( ) ) ;
( function ( ) {
"use strict" ;
/ * *
* The TagPool is an object pool for HTMLAudio tag instances . In Chrome , we have to pre - create the number of HTML
* audio tag instances that we are going to play before we load the data , otherwise the audio stalls .
* ( Note : This seems to be a bug in Chrome )
* # class TagPool
* @ param { String } src The source of the channel .
* @ protected
* /
function TagPool ( src ) {
2014-02-02 19:31:06 -05:00
this . _init ( src ) ;
2014-01-03 13:32:13 -05:00
}
var s = TagPool ;
/ * *
* A hash lookup of each sound channel , indexed by the audio source .
* # property tags
* @ static
* @ protected
* /
s . tags = { } ;
/ * *
* Get a tag pool . If the pool doesn ' t exist , create it .
* # method get
* @ param { String } src The source file used by the audio tag .
* @ static
* @ protected
* /
s . get = function ( src ) {
var channel = s . tags [ src ] ;
if ( channel == null ) {
channel = s . tags [ src ] = new TagPool ( src ) ;
}
return channel ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
* Delete a TagPool and all related tags . Note that if the TagPool does not exist , this will fail .
* # method remove
* @ param { String } src The source for the tag
* @ return { Boolean } If the TagPool was deleted .
* @ static
* /
s . remove = function ( src ) {
var channel = s . tags [ src ] ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return false ; }
2014-01-03 13:32:13 -05:00
channel . removeAll ( ) ;
delete ( s . tags [ src ] ) ;
return true ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
* Delete all TagPools and all related tags .
* # method removeAll
* @ static
* /
s . removeAll = function ( ) {
for ( var channel in s . tags ) {
s . tags [ channel ] . removeAll ( ) ; // this stops and removes all active instances
}
s . tags = { } ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
* Get a tag instance . This is a shortcut method .
* # method getInstance
* @ param { String } src The source file used by the audio tag .
* @ static
* @ protected
* /
s . getInstance = function ( src ) {
var channel = s . tags [ src ] ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return null ; }
2014-01-03 13:32:13 -05:00
return channel . get ( ) ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
* Return a tag instance . This is a shortcut method .
* # method setInstance
* @ param { String } src The source file used by the audio tag .
* @ param { HTMLElement } tag Audio tag to set .
* @ static
* @ protected
* /
s . setInstance = function ( src , tag ) {
var channel = s . tags [ src ] ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return null ; }
2014-01-03 13:32:13 -05:00
return channel . set ( tag ) ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
2014-11-18 18:26:26 -05:00
* Gets the duration of the src audio in milliseconds
* # method getDuration
* @ param { String } src The source file used by the audio tag .
* @ return { Number } Duration of src in milliseconds
2014-01-03 13:32:13 -05:00
* /
2014-11-18 18:26:26 -05:00
s . getDuration = function ( src ) {
2014-01-03 13:32:13 -05:00
var channel = s . tags [ src ] ;
2014-11-18 18:26:26 -05:00
if ( channel == null ) { return 0 ; }
return channel . getDuration ( ) ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
var p = TagPool . prototype ;
2014-11-18 18:26:26 -05:00
p . constructor = TagPool ;
2014-01-03 13:32:13 -05:00
/ * *
* The source of the tag pool .
* # property src
* @ type { String }
* @ protected
* /
p . src = null ;
/ * *
* The total number of HTMLAudio tags in this pool . This is the maximum number of instance of a certain sound
* that can play at one time .
* # property length
* @ type { Number }
* @ default 0
* @ protected
* /
p . length = 0 ;
/ * *
* The number of unused HTMLAudio tags .
* # property available
* @ type { Number }
* @ default 0
* @ protected
* /
p . available = 0 ;
/ * *
* A list of all available tags in the pool .
* # property tags
* @ type { Array }
* @ protected
* /
p . tags = null ;
2014-11-18 18:26:26 -05:00
/ * *
* The duration property of all audio tags , converted to milliseconds , which originally is only available on the
* last tag in the tags array because that is the one that is loaded .
* # property
* @ type { Number }
* @ protected
* /
p . duration = 0 ;
2014-01-03 13:32:13 -05:00
// constructor
2014-02-02 19:31:06 -05:00
p . _init = function ( src ) {
2014-01-03 13:32:13 -05:00
this . src = src ;
this . tags = [ ] ;
} ;
/ * *
* Add an HTMLAudio tag into the pool .
* # method add
* @ param { HTMLAudioElement } tag A tag to be used for playback .
* /
p . add = function ( tag ) {
this . tags . push ( tag ) ;
this . length ++ ;
this . available ++ ;
} ;
/ * *
* Remove all tags from the channel . Usually in response to a delete call .
* # method removeAll
* /
p . removeAll = function ( ) {
2014-11-18 18:26:26 -05:00
var tag ;
2014-01-03 13:32:13 -05:00
while ( this . length -- ) {
2014-11-18 18:26:26 -05:00
tag = this . tags [ this . length ] ;
if ( tag . parentNode ) {
tag . parentNode . removeChild ( tag ) ;
}
2014-01-03 13:32:13 -05:00
delete ( this . tags [ this . length ] ) ; // NOTE that the audio playback is already stopped by this point
}
this . src = null ;
this . tags . length = 0 ;
} ;
/ * *
* Get an HTMLAudioElement for immediate playback . This takes it out of the pool .
* # method get
* @ return { HTMLAudioElement } An HTML audio tag .
* /
p . get = function ( ) {
2014-11-18 18:26:26 -05:00
if ( this . tags . length == 0 ) { return null ; }
2014-01-03 13:32:13 -05:00
this . available = this . tags . length ;
var tag = this . tags . pop ( ) ;
2014-11-18 18:26:26 -05:00
if ( tag . parentNode == null ) { document . body . appendChild ( tag ) ; }
2014-01-03 13:32:13 -05:00
return tag ;
} ;
/ * *
* Put an HTMLAudioElement back in the pool for use .
* # method set
* @ param { HTMLAudioElement } tag HTML audio tag
* /
p . set = function ( tag ) {
var index = createjs . indexOf ( this . tags , tag ) ;
2014-11-18 18:26:26 -05:00
if ( index == - 1 ) { this . tags . push ( tag ) ; }
2014-01-03 13:32:13 -05:00
this . available = this . tags . length ;
} ;
/ * *
2014-11-18 18:26:26 -05:00
* Gets the duration for the src audio and on first call stores it to this . duration
* # method getDuration
* @ return { Number } Duration of the src in milliseconds
2014-01-03 13:32:13 -05:00
* /
2014-11-18 18:26:26 -05:00
p . getDuration = function ( ) {
// this will work because this will be only be run the first time a sound instance is created and before any tags are taken from the pool
if ( ! this . duration ) { this . duration = this . tags [ this . tags . length - 1 ] . duration * 1000 ; }
return this . duration ;
2014-01-03 13:32:13 -05:00
} ;
p . toString = function ( ) {
return "[HTMLAudioPlugin TagPool]" ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
createjs . HTMLAudioPlugin . TagPool = TagPool ;
2014-11-18 18:26:26 -05:00
} ( ) ) ;