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-02-02 19:31:06 -05:00
s . buildDate = /*date*/ "Tue, 21 Jan 2014 18:00:36 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 ;
/ * *
* 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 .
* @ p aram { 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 .
* @ param { Object } [ target ] The object to use as the target property of the event object . This will default to the
* dispatching object . < b > This parameter is deprecated and will be removed . < / b >
* @ return { Boolean } Returns the value of eventObj . defaultPrevented .
* * /
p . dispatchEvent = function ( eventObj , target ) {
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 ) ;
}
// TODO: deprecated. Target param is deprecated, only use case is MouseEvent/mousemove, remove.
eventObj . target = target || this ;
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 ; }
eventObj . currentTarget = this ;
eventObj . eventPhase = eventPhase ;
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 >
* 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 ;
// 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 ) ;
} ;
/ * *
* 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 ) ) ;
} ;
}
} ( ) ) ; / *
* 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 >
* Audio will work in browsers which support HTMLAudioElement ( < a href = "http://caniuse.com/audio" > http : //caniuse.com/audio</a>)
* or WebAudio ( < a href = "http://caniuse.com/audio-api" > http : //caniuse.com/audio-api</a>). A Flash fallback can be added
* as well , which will work in any browser that supports the Flash player .
* @ module SoundJS
* @ main SoundJS
* /
( function ( ) {
"use strict" ;
//TODO: Interface to validate plugins and throw warnings
//TODO: Determine if methods exist on a plugin before calling // OJR this is only an issue if something breaks or user changes something
//TODO: Interface to validate instances and throw warnings
//TODO: Surface errors on audio from all plugins
//TODO: Timeouts // OJR for?
/ * *
* 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
* < a href = "http://preloadjs.com" target = "_blank" > PreloadJS < / a > , t h i s 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
* 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
* load . As a result , it may not play immediately the first time play is called . Use the
* { { # 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 .
*
* createjs . PreloadJS . installPlugin ( createjs . Sound ) ;
*
* < 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 >
* < 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-02-02 19:31:06 -05:00
* DEPRECATED
2014-01-03 13:32:13 -05:00
* This approach has is being replaced by { { # crossLink "Sound/alternateExtensions:property" } } { { / c r o s s L i n k } } , a n d
* support will be removed in the next version .
2014-02-02 19:31:06 -05:00
*
2014-01-03 13:32:13 -05:00
* The character ( or characters ) that are used to split multiple paths from an audio source .
* @ property DELIMITER
* @ type { String }
* @ default |
* @ static
* @ deprecated
* /
s . DELIMITER = "|" ;
/ * *
* 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
* /
s . SUPPORTED _EXTENSIONS = [ "mp3" , "ogg" , "mpeg" , "wav" , "m4a" , "mp4" , "aiff" , "wma" , "mid" ] ; // OJR FlashPlugin does not currently support
/ * *
* 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
/ * *
* An object hash storing sound sources 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
* /
//TODO: Deprecated
/ * *
* REMOVED . Use { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } a n d t h e { { # c r o s s L i n k " S o u n d / f i l e l o a d : e v e n t " } } { { / c r o s s L i n k } }
* event .
* @ property onLoadComplete
* @ type { Function }
* @ deprecated Use addEventListener and the fileload event .
* @ since 0.4 . 0
* /
/ * *
* 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 ) {
if ( ! s . _preloadHash [ src ] ) {
2014-01-03 13:32:13 -05:00
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 ;
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
} ;
} ;
/ * *
* Deprecated in favor of { { # crossLink "Sound/registerPlugins" } } { { / c r o s s L i n k } } w i t h a s i n g l e a r g u m e n t .
* createjs . Sound . registerPlugins ( [ createjs . WebAudioPlugin ] ) ;
*
* @ method registerPlugin
* @ param { Object } plugin The plugin class to install .
* @ return { Boolean } Whether the plugin was successfully initialized .
* @ static
* @ deprecated
* /
s . registerPlugin = function ( plugin ) {
2014-02-02 19:31:06 -05:00
try {
console . log ( "createjs.Sound.registerPlugin has been deprecated. Please use registerPlugins." ) ;
} catch ( err ) {
// you are in IE with the console closed, you monster
}
2014-01-03 13:32:13 -05:00
return s . _registerPlugin ( plugin ) ;
} ;
/ * *
* Register a Sound plugin . Plugins handle the actual playback of audio . 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 } } ) ,
* and are installed if no other plugins are present when the user attempts to start playback or register sound .
* < h4 > Example < / h 4 >
* createjs . FlashPlugin . swfPath = "../src/SoundJS/" ;
* createjs . Sound . _registerPlugin ( createjs . FlashPlugin ) ;
*
* To register multiple plugins , use { { # crossLink "Sound/registerPlugins" } } { { / c r o s s L i n k } } .
*
* @ method _registerPlugin
* @ param { Object } plugin The plugin class to install .
* @ return { Boolean } Whether the plugin was successfully initialized .
* @ static
* @ private
* /
s . _registerPlugin = function ( plugin ) {
2014-02-02 19:31:06 -05:00
s . _pluginsRegistered = true ;
2014-01-03 13:32:13 -05:00
if ( plugin == null ) {
return false ;
}
// 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 ( ) ;
//TODO: Check error on initialization
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 ) {
for ( var i = 0 , l = plugins . length ; i < l ; i ++ ) {
var plugin = plugins [ i ] ;
if ( s . _registerPlugin ( plugin ) ) {
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 ( ) {
if ( s . activePlugin != null ) {
return true ;
}
2014-02-02 19:31:06 -05:00
if ( s . _pluginsRegistered ) {
2014-01-03 13:32:13 -05:00
return false ;
}
if ( s . registerPlugins ( [ createjs . WebAudioPlugin , createjs . HTMLAudioPlugin ] ) ) {
return true ;
}
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 ( ) {
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 ) {
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
* this property is used for other information . The audio channels will default to 1 if no value is found .
* @ param { String } [ path ] A combined basepath and subPath from PreloadJS that has already been prepended to src .
* @ 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
* /
s . initLoad = function ( src , type , id , data , path ) {
// remove path from src so we can continue to support "|" splitting of src files // TODO remove this when "|" is removed
src = src . replace ( path , "" ) ;
var details = s . registerSound ( src , id , data , false , path ) ;
if ( details == null ) {
return false ;
}
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 .
* @ param { Boolean } [ preload = true ] If the sound should be internally preloaded so that it can be played back
2014-02-02 19:31:06 -05:00
* without an external preloader . This is currently used by PreloadJS when loading sounds to disable internal preloading .
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
* /
s . registerSound = function ( src , id , data , preload , basePath ) {
if ( ! s . initializeDefaultPlugins ( ) ) {
return false ;
}
if ( src instanceof Object ) {
basePath = id ; //this assumes preload has not be passed in as a property // OJR check if arguments == 3 would be less fragile
//?? preload = src.preload;
// OJR refactor how data is passed in to make the parameters work better
id = src . id ;
data = src . data ;
src = src . src ;
}
// branch to different parse based on alternate formats setting
if ( s . alternateExtensions . length ) {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath2 ( src , "sound" , id , data ) ;
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath ( src , "sound" , id , data ) ;
2014-01-03 13:32:13 -05:00
}
if ( details == null ) {
return false ;
}
2014-02-02 19:31:06 -05:00
if ( basePath != null ) {
src = basePath + src ;
details . src = basePath + details . src ;
}
2014-01-03 13:32:13 -05:00
if ( id != null ) {
2014-02-02 19:31:06 -05:00
s . _idHash [ id ] = details . src ;
2014-01-03 13:32:13 -05:00
}
var numChannels = null ; // null tells SoundChannel to set this to it's internal maxDefault
if ( data != null ) {
if ( ! isNaN ( data . channels ) ) {
numChannels = parseInt ( data . channels ) ;
}
else if ( ! isNaN ( data ) ) {
numChannels = parseInt ( data ) ;
}
}
var loader = s . activePlugin . register ( details . src , numChannels ) ; // Note only HTML audio uses numChannels
if ( loader != null ) { // all plugins currently return a loader
if ( loader . numChannels != null ) {
numChannels = loader . numChannels ;
} // currently only HTMLAudio returns this
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 ) ) {
data = details . data = numChannels || SoundChannel . maxPerChannel ( ) ;
} else {
data . channels = details . data . channels = numChannels || SoundChannel . maxPerChannel ( ) ;
}
// If the loader returns a tag, return it instead for preloading.
2014-02-02 19:31:06 -05:00
// OJR all loaders currently use tags?
2014-01-03 13:32:13 -05:00
if ( loader . tag != null ) {
details . tag = loader . tag ;
} else if ( loader . src ) {
details . src = loader . src ;
}
// If the loader returns a complete handler, pass it on to the prelaoder.
if ( loader . completeHandler != null ) {
details . completeHandler = loader . completeHandler ;
}
if ( loader . type ) {
details . type = loader . type ;
}
}
if ( preload != false ) {
2014-02-02 19:31:06 -05:00
if ( ! s . _preloadHash [ details . src ] ) {
s . _preloadHash [ details . src ] = [ ] ;
2014-01-03 13:32:13 -05:00
} // we do this so we can store multiple id's and data if needed
2014-02-02 19:31:06 -05:00
s . _preloadHash [ details . src ] . push ( { src : src , id : id , data : data } ) ; // keep this data so we can return it in fileload event
if ( s . _preloadHash [ details . src ] . length == 1 ) {
2014-01-03 13:32:13 -05:00
// if already loaded once, don't load a second time // OJR note this will disallow reloading a sound if loading fails or the source changes
s . activePlugin . preload ( details . src , loader ) ;
} else {
// if src already loaded successfully, return true
2014-02-02 19:31:06 -05:00
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 ++ ) {
returnValues [ i ] = createjs . Sound . registerSound ( manifest [ i ] . src , manifest [ i ] . id , manifest [ i ] . data , manifest [ i ] . preload , basePath ) ;
2014-02-02 19:31:06 -05:00
} // OJR consider removing .preload from args, as it is only used by PreloadJS
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 ) {
if ( s . activePlugin == null ) {
return false ;
}
if ( src instanceof Object ) {
src = src . src ;
}
2014-02-02 19:31:06 -05:00
src = s . _getSrcById ( src ) ;
2014-01-03 13:32:13 -05:00
if ( s . alternateExtensions . length ) {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath2 ( src ) ;
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath ( src ) ;
2014-01-03 13:32:13 -05:00
}
if ( details == null ) {
return false ;
}
if ( basePath != null ) { details . src = basePath + details . src ; }
src = details . src ;
2014-02-02 19:31:06 -05:00
// remove src from _idHash // Note "for in" can be a slow operation
for ( var prop in s . _idHash ) {
if ( s . _idHash [ prop ] == src ) {
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
// remove src from _preloadHash
delete ( s . _preloadHash [ src ] ) ;
2014-01-03 13:32:13 -05:00
// activePlugin cleanup
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 ( ) ;
s . activePlugin . removeAllSounds ( ) ;
} ;
/ * *
* 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 ) {
if ( s . alternateExtensions . length ) {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath2 ( src , "sound" ) ;
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath ( src , "sound" ) ;
2014-01-03 13:32:13 -05:00
}
if ( details ) {
2014-02-02 19:31:06 -05:00
src = s . _getSrcById ( details . src ) ;
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
src = s . _getSrcById ( 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
} ;
/ * *
* Parse the path of a sound , usually from a manifest item . Manifest items support single file paths , as well as
* composite paths using { { # crossLink "Sound/DELIMITER:property" } } { { / c r o s s L i n k } } , w h i c h d e f a u l t s t o " | " . T h e f i r s t p a t h s u p p o r t e d b y t h e
* current browser / plugin will be used .
* NOTE the "|" approach is deprecated and will be removed in the next version
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 .
* @ param { String } [ type ] The type of path . This will typically be "sound" or null .
* @ param { String } [ id ] The user - specified sound ID . This may be null , in which case the src will be used instead .
* @ param { Number | String | Boolean | Object } [ data ] Arbitrary data appended to the sound , usually denoting the
* number of channels for the sound . This method doesn ' t currently do anything with the data property .
* @ 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-02-02 19:31:06 -05:00
s . _parsePath = function ( value , type , id , data ) {
2014-01-03 13:32:13 -05:00
if ( typeof ( value ) != "string" ) { value = value . toString ( ) ; }
var sounds = value . split ( s . DELIMITER ) ;
2014-02-02 19:31:06 -05:00
if ( sounds . length > 1 ) {
try {
console . log ( "createjs.Sound.DELIMITER \"|\" loading approach has been deprecated. Please use the new alternateExtensions property." ) ;
} catch ( err ) {
// you are in IE with the console closed, you monster
}
}
2014-01-03 13:32:13 -05:00
var ret = { type : type || "sound" , id : id , data : data } ;
var c = s . getCapabilities ( ) ;
for ( var i = 0 , l = sounds . length ; i < l ; i ++ ) {
var sound = sounds [ i ] ;
var match = sound . match ( s . FILE _PATTERN ) ;
if ( match == null ) {
return false ;
}
var name = match [ 4 ] ;
var ext = match [ 5 ] ;
if ( c [ ext ] && createjs . indexOf ( s . SUPPORTED _EXTENSIONS , ext ) > - 1 ) {
ret . name = name ;
ret . src = sound ;
ret . extension = ext ;
return ret ;
}
}
return null ;
} ;
2014-02-02 19:31:06 -05:00
// new approach, when old approach is deprecated this will become _parsePath
s . _parsePath2 = function ( value , type , id , data ) {
2014-01-03 13:32:13 -05:00
if ( typeof ( value ) != "string" ) { value = value . toString ( ) ; }
var match = value . match ( s . FILE _PATTERN ) ;
if ( match == null ) {
return false ;
}
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 ) ;
var ret = { type : type || "sound" , id : id , data : data } ;
ret . name = name ;
ret . src = value ;
ret . extension = ext ;
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 ) ;
* }
*
* @ 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 ,
* delay , offset , loop , volume , and pan ( see the above code sample ) .
* @ 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 ) .
* @ 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
* /
s . play = function ( src , interrupt , delay , offset , loop , volume , pan ) {
var instance = s . createInstance ( src ) ;
2014-02-02 19:31:06 -05:00
var ok = s . _playInstance ( instance , interrupt , delay , offset , loop , volume , pan ) ;
2014-01-03 13:32:13 -05:00
if ( ! ok ) {
instance . playFailed ( ) ;
}
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" ) ;
* }
*
* @ method createInstance
* @ param { String } src The src or ID of the audio .
* @ 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
* /
s . createInstance = function ( src ) {
if ( ! s . initializeDefaultPlugins ( ) ) {
2014-02-02 19:31:06 -05:00
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
if ( s . alternateExtensions . length ) {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath2 ( src , "sound" ) ;
2014-01-03 13:32:13 -05:00
} else {
2014-02-02 19:31:06 -05:00
var details = s . _parsePath ( src , "sound" ) ;
2014-01-03 13:32:13 -05:00
}
var instance = null ;
if ( details != null && details . src != null ) {
// make sure that we have a sound channel (sound is registered or previously played)
SoundChannel . create ( details . src ) ;
instance = s . activePlugin . create ( details . src ) ;
} else {
// the src is not supported, so give back a dummy instance.
// This can happen if PreloadJS fails because the plugin does not support the ext, and was passed an id which
2014-02-02 19:31:06 -05:00
// will not get added to the _idHash.
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 ) {
if ( Number ( value ) == null ) {
return false ;
}
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-02-02 19:31:06 -05:00
var instances = this . _instances ; // OJR does this impact garbage collection more than it helps performance?
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
} ;
/ * *
* REMOVED . Please see { { # crossLink "Sound/setMute" } } { { / c r o s s L i n k } } .
* @ method mute
* @ param { Boolean } value Whether the audio should be muted or not .
* @ static
* @ deprecated This function has been deprecated . Please use setMute instead .
* /
/ * *
* 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 ) {
if ( value == null || value == undefined ) {
return false ;
}
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 ( ) ; }
if ( loop == null ) { loop = 0 ; }
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-01-03 13:32:13 -05:00
if ( ! ok ) {
return false ;
}
} 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 ) {
//LM: Should we remove this from the SoundChannel (see finishedPlaying)
2014-02-02 19:31:06 -05:00
var index = createjs . indexOf ( this . _instances , instance ) ;
2014-01-03 13:32:13 -05:00
if ( index > - 1 ) {
2014-02-02 19:31:06 -05:00
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 .
* @ return { String } The source of the sound . Returns null if src has been registered with this id .
* @ protected
* @ static
* /
2014-02-02 19:31:06 -05:00
s . _getSrcById = function ( value ) {
if ( s . _idHash == null || s . _idHash [ value ] == null ) {
2014-01-03 13:32:13 -05:00
return value ;
}
2014-02-02 19:31:06 -05:00
return s . _idHash [ 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-01-03 13:32:13 -05:00
if ( index > - 1 ) {
2014-02-02 19:31:06 -05:00
this . _instances . splice ( index , 1 ) ;
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 ) ;
if ( channel == null ) {
return false ;
}
channel . removeAll ( ) ; // this stops and removes all active instances
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 ) {
SoundChannel . channels [ channel ] . removeAll ( ) ; // this stops and removes all active instances
}
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 ) ;
if ( channel == null ) {
return false ;
}
return channel . add ( instance , interrupt ) ;
} ;
/ * *
* 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 ) ;
if ( channel == null ) {
return false ;
}
channel . remove ( instance ) ;
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 ;
/ * *
* 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 ;
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 .
* /
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 .
* /
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 .
* /
p . remove = function ( instance ) {
2014-02-02 19:31:06 -05:00
var index = createjs . indexOf ( this . _instances , instance ) ;
2014-01-03 13:32:13 -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
* /
p . removeAll = function ( ) {
// Note that stop() removes the item from the list, but we don't want to assume that.
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 .
* /
p . getSlot = function ( interrupt , instance ) {
var target , replacement ;
for ( var i = 0 , l = this . max ; i < l ; i ++ ) {
target = this . get ( i ) ;
// Available Space
if ( target == null ) {
return true ;
} else if ( interrupt == Sound . INTERRUPT _NONE && target . playState != Sound . PLAY _FINISHED ) {
continue ;
}
// First replacement candidate
if ( i == 0 ) {
replacement = target ;
continue ;
}
// Audio is complete or not playing
if ( target . playState == Sound . PLAY _FINISHED ||
target . playState == Sound . PLAY _INTERRUPTED ||
target . playState == Sound . PLAY _FAILED ) {
replacement = target ;
// Audio is a better candidate than the current target, according to playhead
} else if (
( interrupt == Sound . INTERRUPT _EARLY && target . getPosition ( ) < replacement . getPosition ( ) ) ||
( interrupt == Sound . INTERRUPT _LATE && target . getPosition ( ) > replacement . getPosition ( ) ) ) {
replacement = target ;
}
}
if ( replacement != null ) {
2014-02-02 19:31:06 -05:00
replacement . _interrupt ( ) ;
2014-01-03 13:32:13 -05:00
this . remove ( replacement ) ;
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-02-02 19:31:06 -05:00
this . addEventListener = 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 ;
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
BrowserDetect . isIOS = agent . indexOf ( "iPod" ) > - 1 || agent . indexOf ( "iPhone" ) > - 1 || agent . indexOf ( "iPad" ) > - 1 ;
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-01-03 13:32:13 -05:00
if ( s . context == null ) {
return false ;
}
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 {
xhr . open ( "GET" , "fail.fail" , false ) ; // loading non-existant file triggers 404 only if it could load (synchronous call)
} 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 ( ) {
if ( s . _capabilities != null ) {
2014-01-03 13:32:13 -05:00
return ;
}
// Web Audio can be in any formats supported by the audio element, from http://www.w3.org/TR/webaudio/#AudioContext-section,
// therefore tag is still required for the capabilities check
var t = document . createElement ( "audio" ) ;
if ( t . canPlayType == null ) {
return null ;
}
// This check is first because it's what is currently used, but the spec calls for it to be AudioContext so this
// will probably change in time
if ( window . webkitAudioContext ) {
s . context = new webkitAudioContext ( ) ;
} else if ( window . AudioContext ) {
s . context = new AudioContext ( ) ;
} else {
return null ;
}
// this handles if only deprecated Web Audio API calls are supported
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 AudioNodes that all of our source audio will connect to
s . dynamicsCompressorNode = s . context . createDynamicsCompressor ( ) ;
s . dynamicsCompressorNode . connect ( s . context . destination ) ;
s . gainNode = s . context . createGain ( ) ;
s . gainNode . connect ( s . dynamicsCompressorNode ) ;
} ;
/ * *
* 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-01-03 13:32:13 -05:00
* @ protected
* @ since 0.4 . 2
* /
2014-02-02 19:31:06 -05:00
s . _compatibilitySetUp = function ( ) {
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-02-02 19:31:06 -05:00
this . _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
* @ since 0.4 . 1
* /
s . playEmptySound = function ( ) {
// create empty buffer
var buffer = this . context . createBuffer ( 1 , 1 , 22050 ) ;
var source = this . context . createBufferSource ( ) ;
source . buffer = buffer ;
// connect to output (your speakers)
source . connect ( this . context . destination ) ;
// play the file
source . start ( 0 , 0 , 0 ) ;
} ;
var p = WebAudioPlugin . prototype ;
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
* /
// TODO refactor Sound.js so we can use getter setter for volume
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-01-03 13:32:13 -05:00
* @ property dynamicsCompressorNode
* @ type { AudioNode }
* /
p . dynamicsCompressorNode = null ;
/ * *
2014-02-02 19:31:06 -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 } } .
2014-01-03 13:32:13 -05:00
* @ property gainNode
* @ type { AudioGainNode }
* /
p . gainNode = null ;
/ * *
* An object hash used internally to store ArrayBuffers , indexed by the source URI used to load it . This
* 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 ;
this . gainNode = s . gainNode ;
this . dynamicsCompressorNode = s . dynamicsCompressorNode ;
} ;
/ * *
* 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-02-02 19:31:06 -05:00
this . _arrayBuffers [ src ] = true ; // This is needed for PreloadJS
2014-01-03 13:32:13 -05:00
var tag = new createjs . WebAudioPlugin . Loader ( src , this ) ;
return {
tag : tag
} ;
} ;
/ * *
* 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-02-02 19:31:06 -05:00
p . _handlePreloadComplete = function ( ) {
2014-01-03 13:32:13 -05:00
//LM: I would recommend having the Loader include an "event" in the onload, and properly binding this callback.
2014-02-02 19:31:06 -05:00
createjs . Sound . _sendFileLoadEvent ( this . src ) ; // fire event or callback on Sound
2014-01-03 13:32:13 -05:00
// note "this" will reference Loader object
} ;
/ * *
* 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 .
* @ param { Object } instance Not used in this plugin .
* /
p . preload = function ( src , instance ) {
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 .
* @ return { SoundInstance } A sound instance for playback and control .
* /
p . create = function ( src ) {
if ( ! this . isPreloadStarted ( src ) ) {
this . preload ( src ) ;
}
return new createjs . WebAudioPlugin . SoundInstance ( src , this ) ;
} ;
/ * *
* 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 .
* @ param { Object } owner The plugin instance that created this SoundInstance .
* @ extends EventDispatcher
* @ constructor
* /
function SoundInstance ( src , owner ) {
2014-02-02 19:31:06 -05:00
this . _init ( src , owner ) ;
2014-01-03 13:32:13 -05:00
}
var p = SoundInstance . prototype = new createjs . EventDispatcher ( ) ;
/ * *
* 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
/ * *
* The time in milliseconds before the sound starts .
* Note this is handled by { { # crossLink "Sound" } } { { / c r o s s L i n k } } .
2014-02-02 19:31:06 -05:00
* @ property _delay
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _delay = 0 ; // OJR remove this property from SoundInstance as it is not used here?
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 ;
// IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors
try {
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
}
} ) ;
} catch ( e ) {
// dispatch message or error?
} ;
/ * *
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 ;
// IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors
try {
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
}
} ) ;
} catch ( e ) {
// dispatch message or error?
} ;
/ * *
* 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-02-02 19:31:06 -05:00
* @ property _remainingLoops
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _remainingLoops = 0 ;
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 .
* This allows SoundInstance to remove the delay if stop or 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-02-02 19:31:06 -05:00
* @ property _startTime
2014-01-03 13:32:13 -05:00
* @ type { Number }
* @ default 0
* @ protected
* @ since 0.4 . 0
* /
2014-02-02 19:31:06 -05:00
p . _startTime = 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
* /
//TODO: Deprecated
/ * *
* REMOVED . Use { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } a n d t h e { { # c r o s s L i n k " S o u n d I n s t a n c e / s u c c e e d e d : e v e n t " } } { { / c r o s s L i n k } }
* event .
* @ property onPlaySucceeded
* @ type { Function }
* @ deprecated Use addEventListener and the "succeeded" event .
* /
/ * *
* REMOVED . Use { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } a n d t h e { { # c r o s s L i n k " S o u n d I n s t a n c e / i n t e r r u p t e d : e v e n t " } } { { / c r o s s L i n k } }
* event .
* @ property onPlayInterrupted
* @ type { Function }
* @ deprecated Use addEventListener and the "interrupted" event .
* /
/ * *
* REMOVED . Use { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } a n d t h e { { # c r o s s L i n k " S o u n d I n s t a n c e / f a i l e d : e v e n t " } } { { / c r o s s L i n k } }
* event .
* @ property onPlayFailed
* @ type { Function }
* @ deprecated Use addEventListener and the "failed" event .
* /
/ * *
* REMOVED . Use { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } a n d t h e { { # c r o s s L i n k " S o u n d I n s t a n c e / c o m p l e t e : e v e n t " } } { { / c r o s s L i n k } }
* event .
* @ property onComplete
* @ type { Function }
* @ deprecated Use addEventListener and the "complete" event .
* /
/ * *
* REMOVED . Use { { # crossLink "EventDispatcher/addEventListener" } } { { / c r o s s L i n k } } a n d t h e { { # c r o s s L i n k " S o u n d I n s t a n c e / l o o p : e v e n t " } } { { / c r o s s L i n k } }
* event .
* @ property onLoop
* @ type { Function }
* @ deprecated Use addEventListener and the "loop" event .
* /
/ * *
* 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 .
* @ param { Class } owner The plugin that created this instance .
* @ protected
* /
2014-02-02 19:31:06 -05:00
p . _init = function ( src , owner ) {
this . _owner = owner ;
2014-01-03 13:32:13 -05:00
this . src = src ;
2014-02-02 19:31:06 -05:00
this . gainNode = this . _owner . context . createGain ( ) ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . panNode = this . _owner . context . createPanner ( ) ; //TODO test how this affects when we have mono audio
this . panNode . panningModel = this . _owner . _panningModel ;
2014-01-03 13:32:13 -05:00
this . panNode . connect ( this . gainNode ) ;
2014-02-02 19:31:06 -05:00
if ( this . _owner . isPreloadComplete ( this . src ) ) {
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
}
if ( this . gainNode . numberOfOutputs != 0 ) {
this . gainNode . disconnect ( 0 ) ;
} // this works because we only have one connection, and it returns 0 if we've already disconnected it.
// 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-02-02 19:31:06 -05:00
this . _startTime = 0 ; // This is used by getPosition
2014-01-03 13:32:13 -05:00
if ( window . createjs == null ) {
return ;
}
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 ) ;
audioNode . disconnect ( this . panNode ) ;
audioNode = null ; // release reference so Web Audio can handle removing references and garbage collection
}
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-01-03 13:32:13 -05:00
if ( window . createjs == null ) {
return ;
}
2014-02-02 19:31:06 -05:00
if ( ( this . _offset * 1000 ) > this . getDuration ( ) ) { // converting offset to ms
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-02-02 19:31:06 -05:00
var dur = this . _owner . _arrayBuffers [ this . src ] . duration ;
this . sourceNode = this . _createAndPlayAudioNode ( ( this . _owner . context . currentTime - dur ) , this . _offset ) ;
this . _duration = dur * 1000 ; // NOTE *1000 because WebAudio reports everything in seconds but js uses milliseconds
this . _startTime = 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 ) {
this . _sourceNodeNext = this . _createAndPlayAudioNode ( this . _startTime , 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-02-02 19:31:06 -05:00
var currentTime = this . _owner . context . currentTime ;
2014-01-03 13:32:13 -05:00
audioNode . startTime = startTime + audioNode . buffer . duration ; //currentTime + audioNode.buffer.duration - (currentTime - startTime);
audioNode . start ( audioNode . startTime , offset , audioNode . buffer . duration - offset ) ;
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
*
* @ 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-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-01-03 13:32:13 -05:00
if ( window . createjs == null ) {
return ;
}
if ( ! this . src ) {
return ;
}
2014-02-02 19:31:06 -05:00
this . _offset = offset / 1000 ; //convert ms to sec
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-02-02 19:31:06 -05:00
if ( ! this . _paused && this . playState == createjs . Sound . PLAY _SUCCEEDED ) {
this . paused = this . _paused = true ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
this . _offset = this . _owner . context . currentTime - this . _startTime ; // 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
if ( this . gainNode . numberOfOutputs != 0 ) {
this . gainNode . disconnect ( ) ;
} // this works because we only have one connection, and it returns 0 if we've already disconnected it.
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
return true ;
}
return false ;
} ;
/ * *
* 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-02-02 19:31:06 -05:00
if ( ! this . _paused ) {
2014-01-03 13:32:13 -05:00
return false ;
}
2014-02-02 19:31:06 -05:00
this . _handleSoundReady ( null ) ;
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 ;
return true ; // This is always true because even if the volume is not updated, the value is set
} ;
/ * *
* 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
* @ return { Boolean } if the volume was updated .
* @ 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 ;
return true ;
}
return false ;
} ;
/ * *
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 ) {
if ( value == null || value == undefined ) {
return false ;
}
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-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-02-02 19:31:06 -05:00
var pos = this . _owner . context . currentTime - this . _startTime ;
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-02-02 19:31:06 -05:00
this . _offset = value / 1000 ; // 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-02-02 19:31:06 -05:00
if ( ! this . _paused && this . playState == createjs . Sound . PLAY _SUCCEEDED ) {
this . _handleSoundReady ( null ) ;
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 ;
this . _startTime = this . sourceNode . startTime ;
this . _sourceNodeNext = this . _createAndPlayAudioNode ( this . _startTime , 0 ) ;
this . _soundCompleteTimeout = setTimeout ( this . _endedHandler , this . _duration ) ;
2014-01-03 13:32:13 -05:00
}
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 ( "loop" ) ;
2014-01-03 13:32:13 -05:00
return ;
}
if ( window . createjs == null ) {
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 ( ) {
if ( window . createjs == null ) {
return ;
}
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 ;
// 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 original source of the sound , before it is altered with a basePath .
* # property src
* @ type { String }
* /
p . originalSrc = 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 ;
/ * *
* The callback that fires if the load hits an error .
* # property onError
* @ type { Method }
* @ protected
* /
p . onError = null ;
// 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 . originalSrc = src ;
this . owner = owner ;
} ;
/ * *
* Begin loading the content .
* # method load
* @ param { String } src The path to the sound .
* /
p . load = function ( src ) {
if ( src != null ) {
// TODO does this need to set this.originalSrc
this . src = src ;
}
this . request = new XMLHttpRequest ( ) ;
this . request . open ( "GET" , this . src , true ) ;
this . request . responseType = "arraybuffer" ;
this . request . onload = createjs . proxy ( this . handleLoad , this ) ;
this . request . onError = createjs . proxy ( this . handleError , this ) ;
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
* @ param { Number } loaded The loaded amount .
* @ param { Number } total The total amount .
* @ protected
* /
p . handleProgress = function ( loaded , total ) {
this . progress = loaded / total ;
this . onprogress != null && this . onprogress ( { loaded : loaded , total : total , progress : this . progress } ) ;
} ;
/ * *
* 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 . src = this . originalSrc ;
this . owner . addPreloadResults ( this . src , this . result ) ;
this . onload && this . onload ( ) ;
} ;
/ * *
* Errors have been caused by the loader .
* # method handleError
* @ protected
* /
p . handleError = function ( evt ) {
this . owner . removeSound ( this . src ) ;
this . onerror && this . onerror ( evt ) ;
} ;
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 .
*
* < b > IE 9 html 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 if it 's not default. We' ve found default encoding with
* 64 kbps works . < / l i >
* < 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 > 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 >
* < / 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-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
/ * *
* Allows users to enable HTML audio on IOS , which is disabled by default .
* Note this needs to be set before HTMLAudioPlugin is registered with SoundJS .
* This is not recommend because of severe limitations on IOS devices including :
* < 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
* /
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 ( ) {
if ( createjs . Sound . BrowserDetect . isIOS && ! s . enableIOS ) {
return false ;
}
2014-02-02 19:31:06 -05:00
s . _generateCapabilities ( ) ;
2014-01-03 13:32:13 -05:00
var t = s . tag ; // OJR do we still need this check, when cap will already be null if this is the case
2014-02-02 19:31:06 -05:00
if ( t == null || s . _capabilities == null ) {
2014-01-03 13:32:13 -05:00
return false ;
}
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 ( ) {
if ( s . _capabilities != null ) {
2014-01-03 13:32:13 -05:00
return ;
}
var t = s . tag = document . createElement ( "audio" ) ;
if ( t . canPlayType == null ) {
return null ;
}
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 ;
// 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
/ * *
* The default number of instances to allow . Passed back to { { # crossLink "Sound" } } { { / c r o s s L i n k } } w h e n a s o u r c e
* 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 ;
// Proxies, make removing listeners easier.
p . loadedHandler = null ;
/ * *
* 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 ;
var l = instances || this . defaultNumChannels ;
for ( var i = 0 ; i < l ; i ++ ) { // OJR should we be enforcing s.MAX_INSTANCES here? Does the chrome bug still exist, or can we change this code?
2014-02-02 19:31:06 -05:00
tag = this . _createTag ( src ) ;
2014-01-03 13:32:13 -05:00
channel . add ( tag ) ;
}
tag . id = src ; // co-opting id as we need a way to store original src in case it is changed before loading
2014-02-02 19:31:06 -05:00
this . loadedHandler = createjs . proxy ( this . _handleTagLoad , this ) ; // we need this bind to be able to remove event listeners
2014-01-03 13:32:13 -05:00
tag . addEventListener && tag . addEventListener ( "canplaythrough" , this . loadedHandler ) ;
if ( tag . onreadystatechange == null ) {
tag . onreadystatechange = this . loadedHandler ;
} else {
var f = tag . onreadystatechange ;
// OJR will this lose scope?
tag . onreadystatechange = function ( ) {
f ( ) ;
this . loadedHandler ( ) ;
}
}
return {
tag : tag , // Return one instance for preloading purposes
numChannels : l // The default number of channels to make for this Sound or the passed in value
} ;
} ;
2014-02-02 19:31:06 -05:00
// TODO remove this when | approach is removed
2014-01-03 13:32:13 -05:00
/ * *
2014-02-02 19:31:06 -05:00
* Deprecated as this will not be required with new approach to basePath .
2014-01-03 13:32:13 -05:00
* Checks if src was changed on tag used to create instances in TagPool before loading
* Currently PreloadJS does this when a basePath is set , so we are replicating that behavior for internal preloading .
2014-02-02 19:31:06 -05:00
* @ method _handleTagLoad
2014-01-03 13:32:13 -05:00
* @ param event
* @ protected
2014-02-02 19:31:06 -05:00
* @ deprecated
2014-01-03 13:32:13 -05:00
* /
2014-02-02 19:31:06 -05:00
p . _handleTagLoad = function ( event ) {
2014-01-03 13:32:13 -05:00
// cleanup and so we don't send the event more than once
event . target . removeEventListener && event . target . removeEventListener ( "canplaythrough" , this . loadedHandler ) ;
event . target . onreadystatechange = null ;
if ( event . target . src == event . target . id ) { return ; }
// else src has changed before loading, and we need to make the change to TagPool because we pre create tags
createjs . HTMLAudioPlugin . TagPool . checkSrc ( event . target . id ) ;
} ;
/ * *
* 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-02-02 19:31:06 -05:00
this . _audioSources = { } ; // this drops all references, in theory freeing them for garbage collection
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 .
* @ return { SoundInstance } A sound instance for playback and control .
* /
p . create = function ( src ) {
// 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 } ) ;
}
return new createjs . HTMLAudioPlugin . SoundInstance ( src , this ) ;
} ;
/ * *
* 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 .
* @ param { Object } instance An object containing a tag property that is an HTML audio tag used to load src .
* @ since 0.4 . 0
* /
p . preload = function ( src , instance ) {
2014-02-02 19:31:06 -05:00
this . _audioSources [ src ] = true ;
2014-01-03 13:32:13 -05:00
new createjs . HTMLAudioPlugin . Loader ( src , instance . tag ) ;
} ;
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.
function SoundInstance ( src , owner ) {
2014-02-02 19:31:06 -05:00
this . _init ( src , owner ) ;
2014-01-03 13:32:13 -05:00
}
var p = SoundInstance . prototype = new createjs . EventDispatcher ( ) ;
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 ;
p . _delay = 0 ;
2014-01-03 13:32:13 -05:00
p . _volume = 1 ;
// IE8 has Object.defineProperty, but only for DOM objects, so check if fails to suppress errors
try {
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
}
} ) ;
} catch ( e ) {
// dispatch message or error?
} ;
p . pan = 0 ;
2014-02-02 19:31:06 -05:00
p . _duration = 0 ;
p . _remainingLoops = 0 ;
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-01-03 13:32:13 -05:00
p . loopHandler = null ;
// 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 ;
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-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-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-01-03 13:32:13 -05:00
try {
tag . currentTime = 0 ;
} 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 ) ;
2014-01-03 13:32:13 -05:00
if ( window . createjs == null ) {
return ;
}
2014-02-02 19:31:06 -05:00
createjs . Sound . _playFinished ( this ) ;
2014-01-03 13:32:13 -05:00
} ;
2014-02-02 19:31:06 -05:00
p . _interrupt = function ( ) {
2014-01-03 13:32:13 -05:00
if ( this . tag == null ) {
return ;
}
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-02-02 19:31:06 -05:00
this . _cleanUp ( ) ; //LM: Is this redundant?
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
if ( window . createjs == null ) {
return - 1 ;
}
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 ;
this . pan = pan ; // not pan has no effect
2014-02-02 19:31:06 -05:00
this . _updateVolume ( ) ; // note this will set for mute and _masterMute
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 ) {
this . _cleanUp ( ) ; // OJR NOTE this will stop playback, and I think we should remove this and let the developer decide how to handle stalled instances
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
if ( window . createjs == null ) {
return ;
}
2014-02-02 19:31:06 -05:00
// OJR would like a cleaner way to do this in _init, discuss with LM
this . _duration = this . tag . duration * 1000 ; // need this for setPosition on stopped sounds
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-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
if ( this . _offset >= this . getDuration ( ) ) {
2014-01-03 13:32:13 -05:00
this . playFailed ( ) ; // OJR: throw error?
return ;
2014-02-02 19:31:06 -05:00
} else if ( this . _offset > 0 ) {
this . tag . currentTime = this . _offset * 0.001 ;
2014-01-03 13:32:13 -05:00
}
2014-02-02 19:31:06 -05:00
if ( this . _remainingLoops == - 1 ) {
2014-01-03 13:32:13 -05:00
this . tag . loop = true ;
}
2014-02-02 19:31:06 -05:00
if ( this . _remainingLoops != 0 ) {
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
2014-01-03 13:32:13 -05:00
this . tag . loop = true ;
}
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
// Note: when paused by user, we hold a reference to our tag. We do not release it until stopped.
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-02-02 19:31:06 -05:00
if ( ! this . _paused || this . tag == null ) {
2014-01-03 13:32:13 -05:00
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
return true ;
} ;
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-01-03 13:32:13 -05:00
if ( newVolume != this . tag . volume ) {
this . tag . volume = newVolume ;
}
return true ;
} else {
return false ;
}
} ;
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
return true ;
} ;
p . setMute = function ( isMuted ) {
if ( isMuted == null || isMuted == undefined ) {
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
} ;
// Can not set pan in HTML
p . setPan = function ( value ) {
return false ;
} ;
p . getPan = function ( ) {
return 0 ;
} ;
p . getPosition = function ( ) {
if ( this . tag == null ) {
2014-02-02 19:31:06 -05:00
return this . _offset ;
2014-01-03 13:32:13 -05:00
}
return this . tag . currentTime * 1000 ;
} ;
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-01-03 13:32:13 -05:00
try {
this . tag . currentTime = value * 0.001 ;
} catch ( error ) { // Out of range
return false ;
}
2014-02-02 19:31:06 -05:00
this . tag . addEventListener ( createjs . HTMLAudioPlugin . _AUDIO _SEEKED , this . loopHandler , false ) ;
2014-01-03 13:32:13 -05:00
}
return true ;
} ;
p . getDuration = function ( ) { // NOTE this will always return 0 until sound has been played.
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
if ( window . createjs == null ) {
return ;
}
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
} ;
// handles looping functionality
// 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 ;
2014-01-03 13:32:13 -05:00
2014-02-02 19:31:06 -05:00
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 ( ) {
if ( window . createjs == null ) {
return ;
}
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 ;
/ * *
* 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 ) {
this . tag . onreadystatechange = createjs . proxy ( this . sendLoadedEvent , this ) ; // OJR not 100% sure we need this, just copied from PreloadJS
} else {
var f = this . tag . onreadystatechange ;
this . tag . onreadystatechange = function ( ) {
f ( ) ;
this . tag . onreadystatechange = createjs . proxy ( this . sendLoadedEvent , this ) ; // OJR not 100% sure we need this, just copied from PreloadJS
}
}
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-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 ] ;
if ( channel == null ) {
return false ;
}
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 ] ;
if ( channel == null ) {
return null ;
}
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 ] ;
if ( channel == null ) {
return null ;
}
return channel . set ( tag ) ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
/ * *
* A function to check if src has changed in the loaded audio tag .
* This is required because PreloadJS appends a basePath to the src before loading .
* Note this is currently only called when a change is detected
* # method checkSrc
* @ param src the unaltered src that is used to store the channel .
* @ static
* @ protected
* /
s . checkSrc = function ( src ) {
var channel = s . tags [ src ] ;
if ( channel == null ) {
return null ;
}
channel . checkSrcChange ( ) ;
2014-02-02 19:31:06 -05:00
} ;
2014-01-03 13:32:13 -05:00
var p = TagPool . prototype ;
/ * *
* 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 ;
// 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 ( ) {
// This may not be neccessary
while ( this . length -- ) {
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 ( ) {
if ( this . tags . length == 0 ) {
return null ;
}
this . available = this . tags . length ;
var tag = this . tags . pop ( ) ;
if ( tag . parentNode == null ) {
document . body . appendChild ( tag ) ;
}
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 ) ;
if ( index == - 1 ) {
this . tags . push ( tag ) ;
}
this . available = this . tags . length ;
} ;
/ * *
* Make sure the src of all other tags is correct after load .
* This is needed because PreloadJS appends a basePath to src before loading .
* # method checkSrcChange
* /
p . checkSrcChange = function ( ) {
// the last tag always has the latest src after loading
//var i = this.length-1; // this breaks in Firefox because it is not correctly removing an event listener
var i = this . tags . length - 1 ;
if ( i == - 1 ) return ; // CodeCombat addition; sometimes errors in IE without this...
var newSrc = this . tags [ i ] . src ;
while ( i -- ) {
this . tags [ i ] . src = newSrc ;
}
} ;
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 ;
} ( ) ) ;