2013-08-28 06:02:55 +00:00
/ * *
2013-10-01 12:54:29 +00:00
* @ author Richard Davey < rich @ photonstorm . com >
* @ copyright 2013 Photon Storm Ltd .
* @ license { @ link https : //github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
* /
/ * *
* Phaser loader constructor .
2013-08-28 06:02:55 +00:00
* The Loader handles loading all external content such as Images , Sounds , Texture Atlases and data files .
* It uses a combination of Image ( ) loading and xhr and provides progress and completion callbacks .
2013-10-01 12:54:29 +00:00
* @ class Phaser . Loader
* @ classdesc The Loader handles loading all external content such as Images , Sounds , Texture Atlases and data files .
* It uses a combination of Image ( ) loading and xhr and provides progress and completion callbacks .
* @ constructor
* @ param { Phaser . Game } game - A reference to the currently running game .
2013-08-28 06:02:55 +00:00
* /
Phaser . Loader = function ( game ) {
2013-11-25 04:40:04 +00:00
/ * *
2013-10-01 12:54:29 +00:00
* @ property { Phaser . Game } game - Local reference to game .
2013-11-25 04:40:04 +00:00
* /
this . game = game ;
/ * *
2013-11-26 15:29:03 +00:00
* @ property { array } _fileList - Contains all the assets file infos .
2013-11-25 04:40:04 +00:00
* @ private
* /
2013-11-26 15:29:03 +00:00
this . _fileList = [ ] ;
2013-11-25 04:40:04 +00:00
/ * *
2013-11-26 15:29:03 +00:00
* @ property { number } _fileIndex - The index of the current file being loaded .
2013-11-25 04:40:04 +00:00
* @ private
* /
2013-11-26 15:29:03 +00:00
this . _fileIndex = 0 ;
2013-11-25 04:40:04 +00:00
/ * *
* @ property { number } _progressChunk - Indicates assets loading progress . ( from 0 to 100 )
* @ private
* @ default
* /
this . _progressChunk = 0 ;
/ * *
* @ property { XMLHttpRequest } - An XMLHttpRequest object used for loading text and audio data .
* @ private
* /
this . _xhr = new XMLHttpRequest ( ) ;
/ * *
* @ property { boolean } isLoading - True if the Loader is in the process of loading the queue .
* @ default
* /
this . isLoading = false ;
/ * *
* @ property { boolean } hasLoaded - True if all assets in the queue have finished loading .
* @ default
* /
this . hasLoaded = false ;
2013-08-28 06:02:55 +00:00
2013-11-25 04:40:04 +00:00
/ * *
* @ property { number } progress - The Load progress percentage value ( from 0 to 100 )
* @ default
2013-10-01 12:54:29 +00:00
* /
2013-11-25 04:40:04 +00:00
this . progress = 0 ;
2013-08-28 06:02:55 +00:00
2013-11-25 04:40:04 +00:00
/ * *
* You can optionally link a sprite to the preloader .
* If you do so the Sprite ' s width or height will be cropped based on the percentage loaded .
* @ property { Description } preloadSprite
* @ default
2013-10-01 12:54:29 +00:00
* /
2013-11-25 04:40:04 +00:00
this . preloadSprite = null ;
/ * *
* @ property { string } crossOrigin - The crossOrigin value applied to loaded images
* /
this . crossOrigin = '' ;
/ * *
* If you want to append a URL before the path of any asset you can set this here .
* Useful if you need to allow an asset url to be configured outside of the game code .
* MUST have / on the end of it !
* @ property { string } baseURL
* @ default
* /
this . baseURL = '' ;
/ * *
* @ property { Phaser . Signal } onFileComplete - Event signal .
* /
this . onFileComplete = new Phaser . Signal ( ) ;
/ * *
* @ property { Phaser . Signal } onFileError - Event signal .
* /
this . onFileError = new Phaser . Signal ( ) ;
/ * *
* @ property { Phaser . Signal } onLoadStart - Event signal .
* /
this . onLoadStart = new Phaser . Signal ( ) ;
/ * *
* @ property { Phaser . Signal } onLoadComplete - Event signal .
* /
this . onLoadComplete = new Phaser . Signal ( ) ;
2013-09-10 19:40:34 +00:00
} ;
/ * *
2013-10-02 14:05:55 +00:00
* @ constant
* @ type { number }
* /
2013-09-10 19:40:34 +00:00
Phaser . Loader . TEXTURE _ATLAS _JSON _ARRAY = 0 ;
2013-10-02 14:05:55 +00:00
/ * *
* @ constant
* @ type { number }
* /
2013-09-10 19:40:34 +00:00
Phaser . Loader . TEXTURE _ATLAS _JSON _HASH = 1 ;
2013-10-02 14:05:55 +00:00
/ * *
* @ constant
* @ type { number }
* /
2013-09-10 19:40:34 +00:00
Phaser . Loader . TEXTURE _ATLAS _XML _STARLING = 2 ;
Phaser . Loader . prototype = {
2013-10-02 14:05:55 +00:00
2013-11-25 04:40:04 +00:00
/ * *
* You can set a Sprite to be a "preload" sprite by passing it to this method .
* A "preload" sprite will have its width or height crop adjusted based on the percentage of the loader in real - time .
* This allows you to easily make loading bars for games .
*
* @ method Phaser . Loader # setPreloadSprite
2013-10-02 14:05:55 +00:00
* @ param { Phaser . Sprite } sprite - The sprite that will be cropped during the load .
* @ param { number } [ direction = 0 ] - A value of zero means the sprite width will be cropped , a value of 1 means its height will be cropped .
2013-10-01 12:54:29 +00:00
* /
2013-11-25 04:40:04 +00:00
setPreloadSprite : function ( sprite , direction ) {
direction = direction || 0 ;
this . preloadSprite = { sprite : sprite , direction : direction , width : sprite . width , height : sprite . height , crop : null } ;
if ( direction === 0 )
{
// Horizontal crop
this . preloadSprite . crop = new Phaser . Rectangle ( 0 , 0 , 1 , sprite . height ) ;
}
else
{
// Vertical crop
this . preloadSprite . crop = new Phaser . Rectangle ( 0 , 0 , sprite . width , 1 ) ;
}
sprite . crop = this . preloadSprite . crop ;
sprite . cropEnabled = true ;
} ,
/ * *
* Check whether asset exists with a specific key .
*
* @ method Phaser . Loader # checkKeyExists
2013-11-26 15:29:03 +00:00
* @ param { string } type - The type asset you want to check .
2013-11-25 04:40:04 +00:00
* @ param { string } key - Key of the asset you want to check .
* @ return { boolean } Return true if exists , otherwise return false .
* /
2013-11-26 15:29:03 +00:00
checkKeyExists : function ( type , key ) {
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( this . _fileList . length > 0 )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
for ( var i = 0 ; i < this . _fileList . length ; i ++ )
{
if ( this . _fileList [ i ] . type === type && this . _fileList [ i ] . key === key )
{
return true ;
}
}
2013-11-25 04:40:04 +00:00
}
2013-11-26 15:29:03 +00:00
return false ;
} ,
/ * *
* Gets the asset that is queued for load .
*
* @ method Phaser . Loader # getAsset
* @ param { string } type - The type asset you want to check .
* @ param { string } key - Key of the asset you want to check .
* @ return { any } Returns an object if found that has 2 properties : index and file . Otherwise false .
* /
getAsset : function ( type , key ) {
if ( this . _fileList . length > 0 )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
for ( var i = 0 ; i < this . _fileList . length ; i ++ )
{
if ( this . _fileList [ i ] . type === type && this . _fileList [ i ] . key === key )
{
return { index : i , file : this . _fileList [ i ] } ;
}
}
2013-11-25 04:40:04 +00:00
}
2013-11-26 15:29:03 +00:00
return false ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
2013-11-26 15:29:03 +00:00
* Reset loader , this will remove the load queue .
2013-11-25 04:40:04 +00:00
*
* @ method Phaser . Loader # reset
* /
reset : function ( ) {
this . preloadSprite = null ;
this . isLoading = false ;
2013-11-26 15:29:03 +00:00
this . _fileList . length = 0 ;
this . _fileIndex = 0 ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Internal function that adds a new entry to the file list . Do not call directly .
*
* @ method Phaser . Loader # addToFileList
2013-11-26 15:29:03 +00:00
* @ param { string } type - The type of resource to add to the list ( image , audio , xml , etc ) .
* @ param { string } key - The unique Cache ID key of this resource .
* @ param { string } url - The URL the asset will be loaded from .
* @ param { object } properties - Any additional properties needed to load the file .
2013-11-25 04:40:04 +00:00
* @ protected
* /
addToFileList : function ( type , key , url , properties ) {
var entry = {
type : type ,
key : key ,
url : url ,
data : null ,
error : false ,
loaded : false
} ;
if ( typeof properties !== "undefined" )
{
for ( var prop in properties )
{
entry [ prop ] = properties [ prop ] ;
}
}
2013-11-26 15:29:03 +00:00
if ( this . checkKeyExists ( type , key ) === false )
{
this . _fileList . push ( entry ) ;
}
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
} ,
/ * *
* Internal function that replaces an existing entry in the file list with a new one . Do not call directly .
*
* @ method Phaser . Loader # replaceInFileList
* @ param { string } type - The type of resource to add to the list ( image , audio , xml , etc ) .
* @ param { string } key - The unique Cache ID key of this resource .
* @ param { string } url - The URL the asset will be loaded from .
* @ param { object } properties - Any additional properties needed to load the file .
* @ protected
* /
replaceInFileList : function ( type , key , url , properties ) {
var entry = {
type : type ,
key : key ,
url : url ,
data : null ,
error : false ,
loaded : false
} ;
if ( typeof properties !== "undefined" )
{
for ( var prop in properties )
{
entry [ prop ] = properties [ prop ] ;
}
}
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( this . checkKeyExists ( type , key ) === false )
{
this . _fileList . push ( entry ) ;
}
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Add an image to the Loader .
*
* @ method Phaser . Loader # image
* @ param { string } key - Unique asset key of this image file .
* @ param { string } url - URL of image file .
2013-11-26 15:29:03 +00:00
* @ param { boolean } [ overwrite = false ] - If an unloaded file with a matching key already exists in the queue , this entry will overwrite it .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
image : function ( key , url , overwrite ) {
if ( typeof overwrite === "undefined" ) { overwrite = false ; }
2013-11-26 15:29:03 +00:00
if ( overwrite )
{
this . replaceInFileList ( 'image' , key , url ) ;
}
else
2013-11-25 04:40:04 +00:00
{
this . addToFileList ( 'image' , key , url ) ;
}
return this ;
} ,
/ * *
* Add a text file to the Loader .
*
* @ method Phaser . Loader # text
* @ param { string } key - Unique asset key of the text file .
* @ param { string } url - URL of the text file .
2013-11-26 15:29:03 +00:00
* @ param { boolean } [ overwrite = false ] - If an unloaded file with a matching key already exists in the queue , this entry will overwrite it .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
text : function ( key , url , overwrite ) {
if ( typeof overwrite === "undefined" ) { overwrite = false ; }
2013-11-26 15:29:03 +00:00
if ( overwrite )
{
this . replaceInFileList ( 'text' , key , url ) ;
}
else
2013-11-25 04:40:04 +00:00
{
this . addToFileList ( 'text' , key , url ) ;
}
return this ;
} ,
2013-11-28 05:43:35 +00:00
/ * *
* Add a JavaScript file to the Loader . Once loaded the JavaScript file will be automatically turned into a script tag ( and executed ) , so be careful what you load !
*
* @ method Phaser . Loader # script
* @ param { string } key - Unique asset key of the script file .
* @ param { string } url - URL of the JavaScript file .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-28 05:43:35 +00:00
* /
script : function ( key , url ) {
this . addToFileList ( 'script' , key , url ) ;
return this ;
} ,
2013-11-25 04:40:04 +00:00
/ * *
* Add a new sprite sheet to the loader .
*
* @ method Phaser . Loader # spritesheet
* @ param { string } key - Unique asset key of the sheet file .
* @ param { string } url - URL of the sheet file .
* @ param { number } frameWidth - Width of each single frame .
* @ param { number } frameHeight - Height of each single frame .
* @ param { number } [ frameMax = - 1 ] - How many frames in this sprite sheet . If not specified it will divide the whole image into frames .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
spritesheet : function ( key , url , frameWidth , frameHeight , frameMax ) {
if ( typeof frameMax === "undefined" ) { frameMax = - 1 ; }
2013-11-26 15:29:03 +00:00
this . addToFileList ( 'spritesheet' , key , url , { frameWidth : frameWidth , frameHeight : frameHeight , frameMax : frameMax } ) ;
2013-11-25 04:40:04 +00:00
return this ;
} ,
/ * *
* Add a new tile set to the loader . These are used in the rendering of tile maps .
*
* @ method Phaser . Loader # tileset
* @ param { string } key - Unique asset key of the tileset file .
* @ param { string } url - URL of the tileset .
* @ param { number } tileWidth - Width of each single tile in pixels .
* @ param { number } tileHeight - Height of each single tile in pixels .
* @ param { number } [ tileMax = - 1 ] - How many tiles in this tileset . If not specified it will divide the whole image into tiles .
* @ param { number } [ tileMargin = 0 ] - If the tiles have been drawn with a margin , specify the amount here .
* @ param { number } [ tileSpacing = 0 ] - If the tiles have been drawn with spacing between them , specify the amount here .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
tileset : function ( key , url , tileWidth , tileHeight , tileMax , tileMargin , tileSpacing ) {
if ( typeof tileMax === "undefined" ) { tileMax = - 1 ; }
if ( typeof tileMargin === "undefined" ) { tileMargin = 0 ; }
if ( typeof tileSpacing === "undefined" ) { tileSpacing = 0 ; }
2013-11-26 15:29:03 +00:00
this . addToFileList ( 'tileset' , key , url , { tileWidth : tileWidth , tileHeight : tileHeight , tileMax : tileMax , tileMargin : tileMargin , tileSpacing : tileSpacing } ) ;
2013-11-25 04:40:04 +00:00
return this ;
} ,
/ * *
* Add a new audio file to the loader .
*
* @ method Phaser . Loader # audio
* @ param { string } key - Unique asset key of the audio file .
* @ param { Array | string } urls - An array containing the URLs of the audio files , i . e . : [ 'jump.mp3' , 'jump.ogg' , 'jump.m4a' ] or a single string containing just one URL .
* @ param { boolean } autoDecode - When using Web Audio the audio files can either be decoded at load time or run - time . They can 't be played until they are decoded, but this let' s you control when that happens . Decoding is a non - blocking async process .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
audio : function ( key , urls , autoDecode ) {
if ( typeof autoDecode === "undefined" ) { autoDecode = true ; }
2013-11-26 15:29:03 +00:00
this . addToFileList ( 'audio' , key , urls , { buffer : null , autoDecode : autoDecode } ) ;
2013-11-25 04:40:04 +00:00
return this ;
} ,
/ * *
* Add a new tilemap loading request .
*
* @ method Phaser . Loader # tilemap
* @ param { string } key - Unique asset key of the tilemap data .
* @ param { string } [ mapDataURL ] - The url of the map data file ( csv / json )
2013-11-28 14:22:47 +00:00
* @ param { object } [ mapData ] - An optional JSON data object . If given then the mapDataURL is ignored and this JSON object is used for map data instead .
* @ param { string } [ format = Phaser . Tilemap . CSV ] - The format of the map data . Either Phaser . Tilemap . CSV or Phaser . Tilemap . TILED _JSON .
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
tilemap : function ( key , mapDataURL , mapData , format ) {
if ( typeof mapDataURL === "undefined" ) { mapDataURL = null ; }
if ( typeof mapData === "undefined" ) { mapData = null ; }
if ( typeof format === "undefined" ) { format = Phaser . Tilemap . CSV ; }
if ( mapDataURL == null && mapData == null )
{
console . warn ( 'Phaser.Loader.tilemap - Both mapDataURL and mapData are null. One must be set.' ) ;
return this ;
}
2013-11-28 14:22:47 +00:00
// A map data object has been given
if ( mapData )
2013-11-26 15:29:03 +00:00
{
switch ( format )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
// A csv string or object has been given
case Phaser . Tilemap . CSV :
break ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
// An xml string or object has been given
case Phaser . Tilemap . TILED _JSON :
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( typeof mapData === 'string' )
{
mapData = JSON . parse ( mapData ) ;
}
break ;
2013-11-25 04:40:04 +00:00
}
2013-11-26 15:29:03 +00:00
this . game . cache . addTilemap ( key , null , mapData , format ) ;
2013-11-25 04:40:04 +00:00
}
2013-11-28 14:22:47 +00:00
else
{
this . addToFileList ( 'tilemap' , key , mapDataURL , { format : format } ) ;
}
2013-11-25 04:40:04 +00:00
return this ;
} ,
/ * *
* Add a new bitmap font loading request .
*
* @ method Phaser . Loader # bitmapFont
* @ param { string } key - Unique asset key of the bitmap font .
* @ param { string } textureURL - The url of the font image file .
* @ param { string } [ xmlURL ] - The url of the font data file ( xml / fnt )
* @ param { object } [ xmlData ] - An optional XML data object .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
bitmapFont : function ( key , textureURL , xmlURL , xmlData ) {
if ( typeof xmlURL === "undefined" ) { xmlURL = null ; }
if ( typeof xmlData === "undefined" ) { xmlData = null ; }
2013-11-26 15:29:03 +00:00
// A URL to a json/xml file has been given
if ( xmlURL )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
this . addToFileList ( 'bitmapfont' , key , textureURL , { xmlURL : xmlURL } ) ;
}
else
{
// An xml string or object has been given
if ( typeof xmlData === 'string' )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
var xml ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
try {
if ( window [ 'DOMParser' ] )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
var domparser = new DOMParser ( ) ;
xml = domparser . parseFromString ( xmlData , "text/xml" ) ;
2013-11-25 04:40:04 +00:00
}
else
{
2013-11-26 15:29:03 +00:00
xml = new ActiveXObject ( "Microsoft.XMLDOM" ) ;
xml . async = 'false' ;
xml . loadXML ( xmlData ) ;
2013-11-25 04:40:04 +00:00
}
}
2013-11-26 15:29:03 +00:00
catch ( e )
{
xml = undefined ;
}
if ( ! xml || ! xml . documentElement || xml . getElementsByTagName ( "parsererror" ) . length )
{
throw new Error ( "Phaser.Loader. Invalid Bitmap Font XML given" ) ;
}
else
{
this . addToFileList ( 'bitmapfont' , key , textureURL , { xmlURL : null , xmlData : xml } ) ;
}
2013-11-25 04:40:04 +00:00
}
}
return this ;
} ,
/ * *
* Add a new texture atlas to the loader . This atlas uses the JSON Array data format .
*
* @ method Phaser . Loader # atlasJSONArray
2013-11-26 15:29:03 +00:00
* @ param { string } key - Unique asset key of the texture atlas file .
* @ param { string } textureURL - The url of the texture atlas image file .
* @ param { string } [ atlasURL ] - The url of the texture atlas data file ( json / xml ) . You don ' t need this if you are passing an atlasData object instead .
* @ param { object } [ atlasData ] - A JSON or XML data object . You don ' t need this if the data is being loaded from a URL .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
atlasJSONArray : function ( key , textureURL , atlasURL , atlasData ) {
return this . atlas ( key , textureURL , atlasURL , atlasData , Phaser . Loader . TEXTURE _ATLAS _JSON _ARRAY ) ;
} ,
/ * *
* Add a new texture atlas to the loader . This atlas uses the JSON Hash data format .
*
* @ method Phaser . Loader # atlasJSONHash
2013-11-26 15:29:03 +00:00
* @ param { string } key - Unique asset key of the texture atlas file .
* @ param { string } textureURL - The url of the texture atlas image file .
* @ param { string } [ atlasURL ] - The url of the texture atlas data file ( json / xml ) . You don ' t need this if you are passing an atlasData object instead .
* @ param { object } [ atlasData ] - A JSON or XML data object . You don ' t need this if the data is being loaded from a URL .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
atlasJSONHash : function ( key , textureURL , atlasURL , atlasData ) {
return this . atlas ( key , textureURL , atlasURL , atlasData , Phaser . Loader . TEXTURE _ATLAS _JSON _HASH ) ;
} ,
/ * *
* Add a new texture atlas to the loader . This atlas uses the Starling XML data format .
*
* @ method Phaser . Loader # atlasXML
2013-11-26 15:29:03 +00:00
* @ param { string } key - Unique asset key of the texture atlas file .
* @ param { string } textureURL - The url of the texture atlas image file .
* @ param { string } [ atlasURL ] - The url of the texture atlas data file ( json / xml ) . You don ' t need this if you are passing an atlasData object instead .
* @ param { object } [ atlasData ] - A JSON or XML data object . You don ' t need this if the data is being loaded from a URL .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
atlasXML : function ( key , textureURL , atlasURL , atlasData ) {
return this . atlas ( key , textureURL , atlasURL , atlasData , Phaser . Loader . TEXTURE _ATLAS _XML _STARLING ) ;
} ,
/ * *
* Add a new texture atlas to the loader .
*
* @ method Phaser . Loader # atlas
* @ param { string } key - Unique asset key of the texture atlas file .
* @ param { string } textureURL - The url of the texture atlas image file .
* @ param { string } [ atlasURL ] - The url of the texture atlas data file ( json / xml ) . You don ' t need this if you are passing an atlasData object instead .
* @ param { object } [ atlasData ] - A JSON or XML data object . You don ' t need this if the data is being loaded from a URL .
* @ param { number } [ format ] - A value describing the format of the data , the default is Phaser . Loader . TEXTURE _ATLAS _JSON _ARRAY .
2013-11-28 14:22:47 +00:00
* @ return { Phaser . Loader } This Loader instance .
2013-11-25 04:40:04 +00:00
* /
atlas : function ( key , textureURL , atlasURL , atlasData , format ) {
if ( typeof atlasURL === "undefined" ) { atlasURL = null ; }
if ( typeof atlasData === "undefined" ) { atlasData = null ; }
if ( typeof format === "undefined" ) { format = Phaser . Loader . TEXTURE _ATLAS _JSON _ARRAY ; }
2013-11-26 15:29:03 +00:00
// A URL to a json/xml file has been given
if ( atlasURL )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
this . addToFileList ( 'textureatlas' , key , textureURL , { atlasURL : atlasURL , format : format } ) ;
}
else
{
switch ( format )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
// A json string or object has been given
case Phaser . Loader . TEXTURE _ATLAS _JSON _ARRAY :
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( typeof atlasData === 'string' )
{
atlasData = JSON . parse ( atlasData ) ;
}
break ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
// An xml string or object has been given
case Phaser . Loader . TEXTURE _ATLAS _XML _STARLING :
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( typeof atlasData === 'string' )
{
var xml ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
try {
if ( window [ 'DOMParser' ] )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
var domparser = new DOMParser ( ) ;
xml = domparser . parseFromString ( atlasData , "text/xml" ) ;
2013-11-25 04:40:04 +00:00
}
else
{
2013-11-26 15:29:03 +00:00
xml = new ActiveXObject ( "Microsoft.XMLDOM" ) ;
xml . async = 'false' ;
xml . loadXML ( atlasData ) ;
2013-11-25 04:40:04 +00:00
}
}
2013-11-26 15:29:03 +00:00
catch ( e )
{
xml = undefined ;
}
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( ! xml || ! xml . documentElement || xml . getElementsByTagName ( "parsererror" ) . length )
{
throw new Error ( "Phaser.Loader. Invalid Texture Atlas XML given" ) ;
}
else
{
atlasData = xml ;
}
}
break ;
2013-11-25 04:40:04 +00:00
}
2013-11-26 15:29:03 +00:00
this . addToFileList ( 'textureatlas' , key , textureURL , { atlasURL : null , atlasData : atlasData , format : format } ) ;
2013-11-25 04:40:04 +00:00
}
return this ;
} ,
/ * *
* Remove loading request of a file .
*
* @ method Phaser . Loader # removeFile
2013-11-26 15:29:03 +00:00
* @ param { string } type - The type of resource to add to the list ( image , audio , xml , etc ) .
* @ param { string } key - Key of the file you want to remove .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
removeFile : function ( type , key ) {
var file = this . getAsset ( type , key ) ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( file !== false )
{
this . _fileList . splice ( file . index , 1 ) ;
}
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Remove all file loading requests .
*
* @ method Phaser . Loader # removeAll
* /
removeAll : function ( ) {
2013-11-26 15:29:03 +00:00
this . _fileList . length = 0 ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Start loading the assets . Normally you don ' t need to call this yourself as the StateManager will do so .
*
* @ method Phaser . Loader # start
* /
start : function ( ) {
if ( this . isLoading )
{
return ;
}
this . progress = 0 ;
this . hasLoaded = false ;
this . isLoading = true ;
2013-11-26 15:29:03 +00:00
this . onLoadStart . dispatch ( this . _fileList . length ) ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( this . _fileList . length > 0 )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
this . _fileIndex = 0 ;
this . _progressChunk = 100 / this . _fileList . length ;
2013-11-25 04:40:04 +00:00
this . loadFile ( ) ;
}
else
{
this . progress = 100 ;
this . hasLoaded = true ;
this . onLoadComplete . dispatch ( ) ;
}
} ,
/ * *
* Load files . Private method ONLY used by loader .
*
* @ method Phaser . Loader # loadFile
* @ private
* /
loadFile : function ( ) {
2013-11-28 14:22:47 +00:00
if ( ! this . _fileList [ this . _fileIndex ] )
{
console . warn ( 'Phaser.Loader loadFile invalid index ' + this . _fileIndex ) ;
return ;
}
2013-11-26 15:29:03 +00:00
var file = this . _fileList [ this . _fileIndex ] ;
2013-11-25 04:40:04 +00:00
var _this = this ;
// Image or Data?
switch ( file . type )
{
case 'image' :
case 'spritesheet' :
case 'textureatlas' :
case 'bitmapfont' :
case 'tileset' :
file . data = new Image ( ) ;
file . data . name = file . key ;
file . data . onload = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileComplete ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
file . data . onerror = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileError ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
file . data . crossOrigin = this . crossOrigin ;
file . data . src = this . baseURL + file . url ;
break ;
case 'audio' :
file . url = this . getAudioURL ( file . url ) ;
if ( file . url !== null )
{
// WebAudio or Audio Tag?
if ( this . game . sound . usingWebAudio )
{
this . _xhr . open ( "GET" , this . baseURL + file . url , true ) ;
this . _xhr . responseType = "arraybuffer" ;
this . _xhr . onload = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileComplete ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . onerror = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileError ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . send ( ) ;
}
else if ( this . game . sound . usingAudioTag )
{
if ( this . game . sound . touchLocked )
{
// If audio is locked we can't do this yet, so need to queue this load request. Bum.
file . data = new Audio ( ) ;
file . data . name = file . key ;
file . data . preload = 'auto' ;
file . data . src = this . baseURL + file . url ;
2013-11-26 15:29:03 +00:00
this . fileComplete ( this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
}
else
{
file . data = new Audio ( ) ;
file . data . name = file . key ;
file . data . onerror = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileError ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
file . data . preload = 'auto' ;
file . data . src = this . baseURL + file . url ;
2013-11-26 15:29:03 +00:00
file . data . addEventListener ( 'canplaythrough' , Phaser . GAMES [ this . game . id ] . load . fileComplete ( this . _fileIndex ) , false ) ;
2013-11-25 04:40:04 +00:00
file . data . load ( ) ;
}
}
}
else
{
2013-11-26 15:29:03 +00:00
this . fileError ( this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
}
break ;
case 'tilemap' :
this . _xhr . open ( "GET" , this . baseURL + file . url , true ) ;
this . _xhr . responseType = "text" ;
2013-11-28 14:22:47 +00:00
if ( file . format === Phaser . Tilemap . TILED _JSON )
2013-11-25 04:40:04 +00:00
{
this . _xhr . onload = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . jsonLoadComplete ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
}
2013-11-28 14:22:47 +00:00
else if ( file . format === Phaser . Tilemap . CSV )
2013-11-25 04:40:04 +00:00
{
this . _xhr . onload = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . csvLoadComplete ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
}
else
{
throw new Error ( "Phaser.Loader. Invalid Tilemap format: " + file . format ) ;
}
this . _xhr . onerror = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . dataLoadError ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . send ( ) ;
break ;
case 'text' :
this . _xhr . open ( "GET" , this . baseURL + file . url , true ) ;
this . _xhr . responseType = "text" ;
this . _xhr . onload = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileComplete ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . onerror = function ( ) {
2013-11-26 21:10:01 +00:00
return _this . fileError ( _this . _fileIndex ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . send ( ) ;
break ;
2013-11-28 05:43:35 +00:00
case 'script' :
this . _xhr . open ( "GET" , this . baseURL + file . url , true ) ;
this . _xhr . responseType = "text" ;
this . _xhr . onload = function ( ) {
return _this . fileComplete ( _this . _fileIndex ) ;
} ;
this . _xhr . onerror = function ( ) {
return _this . fileError ( _this . _fileIndex ) ;
} ;
this . _xhr . send ( ) ;
break ;
2013-11-25 04:40:04 +00:00
}
} ,
/ * *
* Private method ONLY used by loader .
* @ method Phaser . Loader # getAudioURL
* @ param { array | string } urls - Either an array of audio file URLs or a string containing a single URL path .
* @ private
* /
getAudioURL : function ( urls ) {
var extension ;
if ( typeof urls === 'string' ) { urls = [ urls ] ; }
for ( var i = 0 ; i < urls . length ; i ++ )
{
extension = urls [ i ] . toLowerCase ( ) ;
extension = extension . substr ( ( Math . max ( 0 , extension . lastIndexOf ( "." ) ) || Infinity ) + 1 ) ;
if ( this . game . device . canPlayAudio ( extension ) )
{
return urls [ i ] ;
}
}
return null ;
} ,
/ * *
2013-11-26 15:29:03 +00:00
* Error occured when loading a file .
2013-11-25 04:40:04 +00:00
*
* @ method Phaser . Loader # fileError
2013-11-26 15:29:03 +00:00
* @ param { number } index - The index of the file in the file queue that errored .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
fileError : function ( index ) {
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
this . _fileList [ index ] . loaded = true ;
this . _fileList [ index ] . error = true ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
this . onFileError . dispatch ( this . _fileList [ index ] . key , this . _fileList [ index ] ) ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
console . warn ( "Phaser.Loader error loading file: " + this . _fileList [ index ] . key + ' from URL ' + this . _fileList [ index ] . url ) ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
this . nextFile ( index , false ) ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Called when a file is successfully loaded .
*
* @ method Phaser . Loader # fileComplete
2013-11-26 15:29:03 +00:00
* @ param { number } index - The index of the file in the file queue that loaded .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
fileComplete : function ( index ) {
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( ! this . _fileList [ index ] )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
console . warn ( 'Phaser.Loader fileComplete invalid index ' + index ) ;
2013-11-25 04:40:04 +00:00
return ;
}
2013-11-26 15:29:03 +00:00
this . _fileList [ index ] . loaded = true ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
var file = this . _fileList [ index ] ;
2013-11-25 04:40:04 +00:00
var loadNext = true ;
var _this = this ;
switch ( file . type )
{
case 'image' :
this . game . cache . addImage ( file . key , file . url , file . data ) ;
break ;
case 'spritesheet' :
this . game . cache . addSpriteSheet ( file . key , file . url , file . data , file . frameWidth , file . frameHeight , file . frameMax ) ;
break ;
case 'tileset' :
this . game . cache . addTileset ( file . key , file . url , file . data , file . tileWidth , file . tileHeight , file . tileMax , file . tileMargin , file . tileSpacing ) ;
break ;
case 'textureatlas' :
if ( file . atlasURL == null )
{
this . game . cache . addTextureAtlas ( file . key , file . url , file . data , file . atlasData , file . format ) ;
}
else
{
// Load the JSON or XML before carrying on with the next file
loadNext = false ;
this . _xhr . open ( "GET" , this . baseURL + file . atlasURL , true ) ;
this . _xhr . responseType = "text" ;
if ( file . format == Phaser . Loader . TEXTURE _ATLAS _JSON _ARRAY || file . format == Phaser . Loader . TEXTURE _ATLAS _JSON _HASH )
{
this . _xhr . onload = function ( ) {
2013-11-26 15:29:03 +00:00
return _this . jsonLoadComplete ( index ) ;
2013-11-25 04:40:04 +00:00
} ;
}
else if ( file . format == Phaser . Loader . TEXTURE _ATLAS _XML _STARLING )
{
this . _xhr . onload = function ( ) {
2013-11-26 15:29:03 +00:00
return _this . xmlLoadComplete ( index ) ;
2013-11-25 04:40:04 +00:00
} ;
}
else
{
throw new Error ( "Phaser.Loader. Invalid Texture Atlas format: " + file . format ) ;
}
this . _xhr . onerror = function ( ) {
2013-11-26 15:29:03 +00:00
return _this . dataLoadError ( index ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . send ( ) ;
}
break ;
case 'bitmapfont' :
if ( file . xmlURL == null )
{
this . game . cache . addBitmapFont ( file . key , file . url , file . data , file . xmlData ) ;
}
else
{
// Load the XML before carrying on with the next file
loadNext = false ;
this . _xhr . open ( "GET" , this . baseURL + file . xmlURL , true ) ;
this . _xhr . responseType = "text" ;
this . _xhr . onload = function ( ) {
2013-11-26 15:29:03 +00:00
return _this . xmlLoadComplete ( index ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . onerror = function ( ) {
2013-11-26 15:29:03 +00:00
return _this . dataLoadError ( index ) ;
2013-11-25 04:40:04 +00:00
} ;
this . _xhr . send ( ) ;
}
break ;
case 'audio' :
if ( this . game . sound . usingWebAudio )
{
file . data = this . _xhr . response ;
this . game . cache . addSound ( file . key , file . url , file . data , true , false ) ;
if ( file . autoDecode )
{
this . game . cache . updateSound ( key , 'isDecoding' , true ) ;
var that = this ;
var key = file . key ;
this . game . sound . context . decodeAudioData ( file . data , function ( buffer ) {
if ( buffer )
{
2013-11-27 16:33:49 +00:00
that . game . cache . decodedSound ( key , buffer ) ;
2013-11-25 04:40:04 +00:00
}
} ) ;
}
}
else
{
file . data . removeEventListener ( 'canplaythrough' , Phaser . GAMES [ this . game . id ] . load . fileComplete ) ;
this . game . cache . addSound ( file . key , file . url , file . data , false , true ) ;
}
break ;
case 'text' :
file . data = this . _xhr . responseText ;
this . game . cache . addText ( file . key , file . url , file . data ) ;
break ;
2013-11-28 05:43:35 +00:00
case 'script' :
file . data = document . createElement ( 'script' ) ;
file . data . language = 'javascript' ;
file . data . type = 'text/javascript' ;
file . data . defer = true ;
file . data . text = this . _xhr . responseText ;
document . head . appendChild ( file . data ) ;
break ;
2013-11-25 04:40:04 +00:00
}
if ( loadNext )
{
2013-11-26 15:29:03 +00:00
this . nextFile ( index , true ) ;
2013-11-25 04:40:04 +00:00
}
} ,
/ * *
* Successfully loaded a JSON file .
*
* @ method Phaser . Loader # jsonLoadComplete
2013-11-26 15:29:03 +00:00
* @ param { number } index - The index of the file in the file queue that loaded .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
jsonLoadComplete : function ( index ) {
2013-11-25 04:40:04 +00:00
2013-11-28 14:22:47 +00:00
if ( ! this . _fileList [ index ] )
{
console . warn ( 'Phaser.Loader jsonLoadComplete invalid index ' + index ) ;
return ;
}
2013-11-26 15:29:03 +00:00
var file = this . _fileList [ index ] ;
2013-11-28 14:22:47 +00:00
var data = JSON . parse ( this . _xhr . responseText ) ;
file . loaded = true ;
2013-11-25 04:40:04 +00:00
2013-11-28 14:22:47 +00:00
if ( file . type === 'tilemap' )
2013-11-25 04:40:04 +00:00
{
this . game . cache . addTilemap ( file . key , file . url , data , file . format ) ;
}
else
{
this . game . cache . addTextureAtlas ( file . key , file . url , file . data , data , file . format ) ;
}
2013-11-26 15:29:03 +00:00
this . nextFile ( index , true ) ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Successfully loaded a CSV file .
*
* @ method Phaser . Loader # csvLoadComplete
2013-11-26 15:29:03 +00:00
* @ param { number } index - The index of the file in the file queue that loaded .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
csvLoadComplete : function ( index ) {
2013-11-25 04:40:04 +00:00
2013-11-28 14:22:47 +00:00
if ( ! this . _fileList [ index ] )
{
console . warn ( 'Phaser.Loader csvLoadComplete invalid index ' + index ) ;
return ;
}
2013-11-26 15:29:03 +00:00
var file = this . _fileList [ index ] ;
2013-11-28 14:22:47 +00:00
var data = this . _xhr . responseText ;
file . loaded = true ;
2013-11-25 04:40:04 +00:00
this . game . cache . addTilemap ( file . key , file . url , data , file . format ) ;
2013-11-26 15:29:03 +00:00
this . nextFile ( index , true ) ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Error occured when load a JSON .
*
* @ method Phaser . Loader # dataLoadError
2013-11-26 15:29:03 +00:00
* @ param { number } index - The index of the file in the file queue that errored .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
dataLoadError : function ( index ) {
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
var file = this . _fileList [ index ] ;
2013-11-25 04:40:04 +00:00
2013-11-28 14:22:47 +00:00
file . loaded = true ;
2013-11-25 04:40:04 +00:00
file . error = true ;
2013-11-26 15:29:03 +00:00
console . warn ( "Phaser.Loader dataLoadError: " + file . key ) ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
this . nextFile ( index , true ) ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Successfully loaded an XML file .
*
* @ method Phaser . Loader # xmlLoadComplete
2013-11-26 15:29:03 +00:00
* @ param { number } index - The index of the file in the file queue that loaded .
2013-11-25 04:40:04 +00:00
* /
2013-11-26 15:29:03 +00:00
xmlLoadComplete : function ( index ) {
2013-11-25 04:40:04 +00:00
var data = this . _xhr . responseText ;
var xml ;
try
{
if ( window [ 'DOMParser' ] )
{
var domparser = new DOMParser ( ) ;
xml = domparser . parseFromString ( data , "text/xml" ) ;
}
else
{
xml = new ActiveXObject ( "Microsoft.XMLDOM" ) ;
xml . async = 'false' ;
xml . loadXML ( data ) ;
}
}
catch ( e )
{
xml = undefined ;
}
if ( ! xml || ! xml . documentElement || xml . getElementsByTagName ( "parsererror" ) . length )
{
throw new Error ( "Phaser.Loader. Invalid XML given" ) ;
}
2013-11-26 15:29:03 +00:00
var file = this . _fileList [ index ] ;
2013-11-28 14:22:47 +00:00
file . loaded = true ;
2013-11-25 04:40:04 +00:00
if ( file . type == 'bitmapfont' )
{
this . game . cache . addBitmapFont ( file . key , file . url , file . data , xml ) ;
}
else if ( file . type == 'textureatlas' )
{
this . game . cache . addTextureAtlas ( file . key , file . url , file . data , xml , file . format ) ;
}
2013-11-26 15:29:03 +00:00
this . nextFile ( index , true ) ;
2013-11-25 04:40:04 +00:00
} ,
/ * *
* Handle loading next file .
*
2013-11-26 15:29:03 +00:00
* @ param { number } previousIndex - Index of the previously loaded asset .
* @ param { boolean } success - Whether the previous asset loaded successfully or not .
2013-11-25 04:40:04 +00:00
* @ private
* /
2013-11-26 15:29:03 +00:00
nextFile : function ( previousIndex , success ) {
2013-11-25 04:40:04 +00:00
this . progress = Math . round ( this . progress + this . _progressChunk ) ;
if ( this . progress > 100 )
{
this . progress = 100 ;
}
if ( this . preloadSprite !== null )
{
if ( this . preloadSprite . direction === 0 )
{
this . preloadSprite . crop . width = Math . floor ( ( this . preloadSprite . width / 100 ) * this . progress ) ;
}
else
{
this . preloadSprite . crop . height = Math . floor ( ( this . preloadSprite . height / 100 ) * this . progress ) ;
}
this . preloadSprite . sprite . crop = this . preloadSprite . crop ;
}
2013-11-26 15:29:03 +00:00
this . onFileComplete . dispatch ( this . progress , this . _fileList [ previousIndex ] . key , success , this . totalLoadedFiles ( ) , this . _fileList . length ) ;
2013-11-25 04:40:04 +00:00
2013-11-26 15:29:03 +00:00
if ( this . totalQueuedFiles ( ) > 0 )
2013-11-25 04:40:04 +00:00
{
2013-11-26 15:29:03 +00:00
this . _fileIndex ++ ;
2013-11-25 04:40:04 +00:00
this . loadFile ( ) ;
}
else
{
this . hasLoaded = true ;
this . isLoading = false ;
this . removeAll ( ) ;
this . onLoadComplete . dispatch ( ) ;
}
2013-11-26 15:29:03 +00:00
} ,
/ * *
* Returns the number of files that have already been loaded , even if they errored .
*
* @ return { number } The number of files that have already been loaded ( even if they errored )
* /
totalLoadedFiles : function ( ) {
var total = 0 ;
2013-11-26 21:10:01 +00:00
for ( var i = 0 ; i < this . _fileList . length ; i ++ )
2013-11-26 15:29:03 +00:00
{
if ( this . _fileList [ i ] . loaded )
{
total ++ ;
}
}
return total ;
} ,
/ * *
* Returns the number of files still waiting to be processed in the load queue . This value decreases as each file is in the queue is loaded .
*
* @ return { number } The number of files that still remain in the load queue .
* /
totalQueuedFiles : function ( ) {
var total = 0 ;
2013-11-26 21:10:01 +00:00
for ( var i = 0 ; i < this . _fileList . length ; i ++ )
2013-11-26 15:29:03 +00:00
{
if ( this . _fileList [ i ] . loaded === false )
{
total ++ ;
}
}
return total ;
2013-11-25 04:40:04 +00:00
}
2013-08-28 06:02:55 +00:00
2013-11-19 23:12:37 +00:00
} ;