phaser/src/loader/File.js

550 lines
16 KiB
JavaScript
Raw Normal View History

2018-02-12 16:01:20 +00:00
/**
* @author Richard Davey <rich@photonstorm.com>
2020-01-15 12:07:09 +00:00
* @copyright 2020 Photon Storm Ltd.
2019-05-10 15:15:04 +00:00
* @license {@link https://opensource.org/licenses/MIT|MIT License}
2018-02-12 16:01:20 +00:00
*/
var Class = require('../utils/Class');
var CONST = require('./const');
2019-01-17 13:04:26 +00:00
var Events = require('./events');
2018-01-26 14:23:00 +00:00
var GetFastValue = require('../utils/object/GetFastValue');
var GetURL = require('./GetURL');
var MergeXHRSettings = require('./MergeXHRSettings');
var XHRLoader = require('./XHRLoader');
var XHRSettings = require('./XHRSettings');
2018-02-07 15:27:21 +00:00
/**
* @classdesc
2018-05-04 15:00:02 +00:00
* The base File class used by all File Types that the Loader can support.
* You shouldn't create an instance of a File directly, but should extend it with your own class, setting a custom type and processing methods.
2018-02-07 15:27:21 +00:00
*
* @class File
2018-10-10 09:49:13 +00:00
* @memberof Phaser.Loader
2018-02-07 15:27:21 +00:00
* @constructor
* @since 3.0.0
*
* @param {Phaser.Loader.LoaderPlugin} loader - The Loader that is going to load this File.
2019-05-09 11:04:54 +00:00
* @param {Phaser.Types.Loader.FileConfig} fileConfig - The file configuration object, as created by the file type.
2018-02-07 15:27:21 +00:00
*/
var File = new Class({
initialize:
function File (loader, fileConfig)
{
/**
* A reference to the Loader that is going to load this file.
*
* @name Phaser.Loader.File#loader
* @type {Phaser.Loader.LoaderPlugin}
* @since 3.0.0
*/
this.loader = loader;
/**
* A reference to the Cache, or Texture Manager, that is going to store this file if it loads.
*
* @name Phaser.Loader.File#cache
* @type {(Phaser.Cache.BaseCache|Phaser.Textures.TextureManager)}
* @since 3.7.0
*/
2018-04-25 22:16:17 +00:00
this.cache = GetFastValue(fileConfig, 'cache', false);
2018-01-26 14:23:00 +00:00
/**
* The file type string (image, json, etc) for sorting within the Loader.
2018-03-19 21:57:46 +00:00
*
* @name Phaser.Loader.File#type
* @type {string}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.type = GetFastValue(fileConfig, 'type', false);
2018-01-26 14:23:00 +00:00
/**
* Unique cache key (unique within its file type)
*
* @name Phaser.Loader.File#key
* @type {string}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.key = GetFastValue(fileConfig, 'key', false);
var loadKey = this.key;
if (loader.prefix && loader.prefix !== '')
{
this.key = loader.prefix + loadKey;
}
if (!this.type || !this.key)
{
throw new Error('Invalid Loader.' + this.type + ' key');
}
var url = GetFastValue(fileConfig, 'url');
if (url === undefined)
{
url = loader.path + loadKey + '.' + GetFastValue(fileConfig, 'extension', '');
}
else if (typeof url === 'string' && !url.match(/^(?:blob:|data:|capacitor:\/\/|http:\/\/|https:\/\/|\/\/)/))
{
url = loader.path + url;
}
2018-01-26 14:23:00 +00:00
/**
* The URL of the file, not including baseURL.
2020-07-14 08:03:07 +00:00
*
* Automatically has Loader.path prepended to it if a string.
*
* Can also be a JavaScript Object, such as the results of parsing JSON data.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#url
2020-07-14 08:03:07 +00:00
* @type {object|string}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.url = url;
2018-01-26 14:23:00 +00:00
/**
2018-05-04 15:00:02 +00:00
* The final URL this file will load from, including baseURL and path.
* Set automatically when the Loader calls 'load' on this file.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#src
* @type {string}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.src = '';
2018-01-26 14:23:00 +00:00
/**
* The merged XHRSettings for this file.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#xhrSettings
2019-05-09 11:04:54 +00:00
* @type {Phaser.Types.Loader.XHRSettingsObject}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.xhrSettings = XHRSettings(GetFastValue(fileConfig, 'responseType', undefined));
if (GetFastValue(fileConfig, 'xhrSettings', false))
{
this.xhrSettings = MergeXHRSettings(this.xhrSettings, GetFastValue(fileConfig, 'xhrSettings', {}));
}
2018-01-26 14:23:00 +00:00
/**
2018-03-28 14:04:09 +00:00
* The XMLHttpRequest instance (as created by XHR Loader) that is loading this File.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#xhrLoader
2018-03-28 14:04:09 +00:00
* @type {?XMLHttpRequest}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.xhrLoader = null;
2018-01-26 14:23:00 +00:00
/**
* The current state of the file. One of the FILE_CONST values.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#state
2020-11-23 10:22:13 +00:00
* @type {number}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.state = (typeof(this.url) === 'function') ? CONST.FILE_POPULATED : CONST.FILE_PENDING;
2018-01-26 14:23:00 +00:00
/**
* The total size of this file.
* Set by onProgress and only if loading via XHR.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#bytesTotal
* @type {number}
2018-01-26 14:23:00 +00:00
* @default 0
* @since 3.0.0
*/
this.bytesTotal = 0;
2018-01-26 14:23:00 +00:00
/**
* Updated as the file loads.
* Only set if loading via XHR.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#bytesLoaded
* @type {number}
2018-01-26 14:23:00 +00:00
* @default -1
* @since 3.0.0
*/
this.bytesLoaded = -1;
2018-01-26 14:23:00 +00:00
/**
* A percentage value between 0 and 1 indicating how much of this file has loaded.
* Only set if loading via XHR.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#percentComplete
* @type {number}
2018-01-26 14:23:00 +00:00
* @default -1
* @since 3.0.0
*/
this.percentComplete = -1;
2018-01-26 14:23:00 +00:00
/**
* For CORs based loading.
* If this is undefined then the File will check BaseLoader.crossOrigin and use that (if set)
*
* @name Phaser.Loader.File#crossOrigin
2018-03-20 15:12:42 +00:00
* @type {(string|undefined)}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.crossOrigin = undefined;
2018-01-26 14:23:00 +00:00
/**
2018-05-04 15:00:02 +00:00
* The processed file data, stored here after the file has loaded.
2018-01-26 14:23:00 +00:00
*
* @name Phaser.Loader.File#data
2018-03-20 16:15:49 +00:00
* @type {*}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.data = undefined;
2018-01-26 14:23:00 +00:00
/**
* A config object that can be used by file types to store transitional data.
*
* @name Phaser.Loader.File#config
2018-05-04 15:00:02 +00:00
* @type {*}
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*/
this.config = GetFastValue(fileConfig, 'config', {});
2018-01-26 14:23:00 +00:00
/**
* If this is a multipart file, i.e. an atlas and its json together, then this is a reference
2018-05-04 15:00:02 +00:00
* to the parent MultiFile. Set and used internally by the Loader or specific file types.
2018-01-26 14:23:00 +00:00
*
2018-05-04 10:33:51 +00:00
* @name Phaser.Loader.File#multiFile
* @type {?Phaser.Loader.MultiFile}
* @since 3.7.0
2018-01-26 14:23:00 +00:00
*/
2018-05-04 10:33:51 +00:00
this.multiFile;
/**
* Does this file have an associated linked file? Such as an image and a normal map.
* Atlases and Bitmap Fonts use the multiFile, because those files need loading together but aren't
* actually bound by data, where-as a linkFile is.
*
* @name Phaser.Loader.File#linkFile
* @type {?Phaser.Loader.File}
* @since 3.7.0
*/
this.linkFile;
},
2018-05-04 15:00:02 +00:00
/**
* Links this File with another, so they depend upon each other for loading and processing.
*
* @method Phaser.Loader.File#setLink
* @since 3.7.0
*
* @param {Phaser.Loader.File} fileB - The file to link to this one.
*/
setLink: function (fileB)
{
this.linkFile = fileB;
fileB.linkFile = this;
},
2018-01-26 14:23:00 +00:00
/**
2018-05-04 15:00:02 +00:00
* Resets the XHRLoader instance this file is using.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#resetXHR
* @since 3.0.0
*/
resetXHR: function ()
{
if (this.xhrLoader)
{
this.xhrLoader.onload = undefined;
this.xhrLoader.onerror = undefined;
this.xhrLoader.onprogress = undefined;
}
},
2018-01-26 14:23:00 +00:00
/**
* Called by the Loader, starts the actual file downloading.
2018-05-04 15:00:02 +00:00
* During the load the methods onLoad, onError and onProgress are called, based on the XHR events.
* You shouldn't normally call this method directly, it's meant to be invoked by the Loader.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#load
* @since 3.0.0
*/
load: function ()
{
if (this.state === CONST.FILE_POPULATED)
{
// Can happen for example in a JSONFile if they've provided a JSON object instead of a URL
this.loader.nextFile(this, true);
}
else
{
this.state = CONST.FILE_LOADING;
this.src = GetURL(this, this.loader.baseURL);
if (this.src.indexOf('data:') === 0)
{
console.warn('Local data URIs are not supported: ' + this.key);
}
else
{
// The creation of this XHRLoader starts the load process going.
// It will automatically call the following, based on the load outcome:
//
2018-05-04 15:00:02 +00:00
// xhr.onload = this.onLoad
// xhr.onerror = this.onError
// xhr.onprogress = this.onProgress
this.xhrLoader = XHRLoader(this, this.loader.xhr);
}
}
},
2018-01-26 14:23:00 +00:00
/**
* Called when the file finishes loading, is sent a DOM ProgressEvent.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#onLoad
* @since 3.0.0
*
* @param {XMLHttpRequest} xhr - The XMLHttpRequest that caused this onload event.
* @param {ProgressEvent} event - The DOM ProgressEvent that resulted from this load.
2018-01-26 14:23:00 +00:00
*/
onLoad: function (xhr, event)
{
// On iOS, Capacitor often runs on a capacitor:// protocol, meaning local files are served from capacitor:// rather than file://
2021-05-24 16:58:30 +00:00
// See: https://github.com/photonstorm/phaser/issues/5685
var isLocalFile = xhr.responseURL && (xhr.responseURL.indexOf('file://') === 0 || xhr.responseURL.indexOf('capacitor://') === 0);
2021-05-24 16:58:30 +00:00
var localFileOk = (isLocalFile && event.target.status === 0);
var success = !(event.target && event.target.status !== 200) || localFileOk;
// Handle HTTP status codes of 4xx and 5xx as errors, even if xhr.onerror was not called.
if (xhr.readyState === 4 && xhr.status >= 400 && xhr.status <= 599)
{
success = false;
}
this.state = CONST.FILE_LOADED;
this.resetXHR();
this.loader.nextFile(this, success);
},
2018-01-26 14:23:00 +00:00
/**
* Called if the file errors while loading, is sent a DOM ProgressEvent.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#onError
* @since 3.0.0
*
* @param {XMLHttpRequest} xhr - The XMLHttpRequest that caused this onload event.
* @param {ProgressEvent} event - The DOM ProgressEvent that resulted from this error.
2018-01-26 14:23:00 +00:00
*/
2018-02-16 19:08:50 +00:00
onError: function ()
{
this.resetXHR();
this.loader.nextFile(this, false);
},
2018-01-26 14:23:00 +00:00
/**
* Called during the file load progress. Is sent a DOM ProgressEvent.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#onProgress
2019-01-17 13:04:26 +00:00
* @fires Phaser.Loader.Events#FILE_PROGRESS
2018-01-26 14:23:00 +00:00
* @since 3.0.0
*
* @param {ProgressEvent} event - The DOM ProgressEvent.
2018-01-26 14:23:00 +00:00
*/
onProgress: function (event)
{
if (event.lengthComputable)
{
this.bytesLoaded = event.loaded;
this.bytesTotal = event.total;
this.percentComplete = Math.min((this.bytesLoaded / this.bytesTotal), 1);
2019-01-17 13:04:26 +00:00
this.loader.emit(Events.FILE_PROGRESS, this, this.percentComplete);
}
},
2018-01-26 14:23:00 +00:00
/**
2018-05-04 15:00:02 +00:00
* Usually overridden by the FileTypes and is called by Loader.nextFile.
* This method controls what extra work this File does with its loaded data, for example a JSON file will parse itself during this stage.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#onProcess
* @since 3.0.0
*/
onProcess: function ()
{
2016-12-07 00:27:56 +00:00
this.state = CONST.FILE_PROCESSING;
2018-05-03 16:12:44 +00:00
this.onProcessComplete();
},
2018-01-26 14:23:00 +00:00
/**
2018-05-04 15:00:02 +00:00
* Called when the File has completed processing.
2018-05-04 10:33:51 +00:00
* Checks on the state of its multifile, if set.
2018-01-26 14:23:00 +00:00
*
* @method Phaser.Loader.File#onProcessComplete
* @since 3.7.0
2018-01-26 14:23:00 +00:00
*/
onProcessComplete: function ()
{
this.state = CONST.FILE_COMPLETE;
2018-05-04 10:33:51 +00:00
if (this.multiFile)
{
2018-05-04 10:33:51 +00:00
this.multiFile.onFileComplete(this);
}
this.loader.fileProcessComplete(this);
},
/**
2018-05-04 15:00:02 +00:00
* Called when the File has completed processing but it generated an error.
2018-05-04 10:33:51 +00:00
* Checks on the state of its multifile, if set.
*
* @method Phaser.Loader.File#onProcessError
* @since 3.7.0
*/
onProcessError: function ()
{
this.state = CONST.FILE_ERRORED;
2018-05-04 10:33:51 +00:00
if (this.multiFile)
{
2018-05-04 10:33:51 +00:00
this.multiFile.onFileFailed(this);
}
this.loader.fileProcessComplete(this);
},
/**
* Checks if a key matching the one used by this file exists in the target Cache or not.
* This is called automatically by the LoaderPlugin to decide if the file can be safely
* loaded or will conflict.
*
* @method Phaser.Loader.File#hasCacheConflict
* @since 3.7.0
*
* @return {boolean} `true` if adding this file will cause a conflict, otherwise `false`.
*/
hasCacheConflict: function ()
{
2018-04-25 22:16:17 +00:00
return (this.cache && this.cache.exists(this.key));
},
/**
* Adds this file to its target cache upon successful loading and processing.
* This method is often overridden by specific file types.
*
* @method Phaser.Loader.File#addToCache
* @since 3.7.0
*/
addToCache: function ()
{
2018-04-25 22:16:17 +00:00
if (this.cache)
{
this.cache.add(this.key, this.data);
}
this.pendingDestroy();
},
2018-05-08 00:12:20 +00:00
/**
* Called once the file has been added to its cache and is now ready for deletion from the Loader.
* It will emit a `filecomplete` event from the LoaderPlugin.
*
* @method Phaser.Loader.File#pendingDestroy
2019-01-17 13:04:26 +00:00
* @fires Phaser.Loader.Events#FILE_COMPLETE
* @fires Phaser.Loader.Events#FILE_KEY_COMPLETE
* @since 3.7.0
*/
pendingDestroy: function (data)
{
if (data === undefined) { data = this.data; }
var key = this.key;
var type = this.type;
2019-01-17 13:04:26 +00:00
this.loader.emit(Events.FILE_COMPLETE, key, type, data);
this.loader.emit(Events.FILE_KEY_COMPLETE + type + '-' + key, key, type, data);
this.loader.flagForRemoval(this);
},
/**
* Destroy this File and any references it holds.
*
* @method Phaser.Loader.File#destroy
* @since 3.7.0
*/
destroy: function ()
{
this.loader = null;
this.cache = null;
this.xhrSettings = null;
2018-05-04 10:33:51 +00:00
this.multiFile = null;
this.linkFile = null;
this.data = null;
}
});
/**
* Static method for creating object URL using URL API and setting it as image 'src' attribute.
* If URL API is not supported (usually on old browsers) it falls back to creating Base64 encoded url using FileReader.
*
* @method Phaser.Loader.File.createObjectURL
* @static
2019-02-12 12:14:26 +00:00
* @since 3.7.0
*
2018-04-18 12:29:22 +00:00
* @param {HTMLImageElement} image - Image object which 'src' attribute should be set to object URL.
* @param {Blob} blob - A Blob object to create an object URL for.
* @param {string} defaultType - Default mime type used if blob type is not available.
*/
File.createObjectURL = function (image, blob, defaultType)
{
if (typeof URL === 'function')
{
image.src = URL.createObjectURL(blob);
}
else
{
var reader = new FileReader();
reader.onload = function ()
{
image.removeAttribute('crossOrigin');
image.src = 'data:' + (blob.type || defaultType) + ';base64,' + reader.result.split(',')[1];
};
reader.onerror = image.onerror;
reader.readAsDataURL(blob);
}
};
/**
* Static method for releasing an existing object URL which was previously created
* by calling {@link File#createObjectURL} method.
*
* @method Phaser.Loader.File.revokeObjectURL
* @static
2019-02-12 12:14:26 +00:00
* @since 3.7.0
*
2018-04-18 12:29:22 +00:00
* @param {HTMLImageElement} image - Image object which 'src' attribute should be revoked.
*/
File.revokeObjectURL = function (image)
{
if (typeof URL === 'function')
{
URL.revokeObjectURL(image.src);
}
};
module.exports = File;