// Vendor // @ts-ignore import FontFaceObserver from 'fontfaceobserver-es'; // Events import { eventEmitter } from '../events/EventEmitter'; // Features import getBrowserType from '../features/browserFeatures/getBrowserType'; import getWebGLFeatures from '../features/browserFeatures/getWebGLFeatures'; import isImageBitmapSupported from '../features/browserFeatures/isImageBitmapSupported'; import isImageDecodeSupported from '../features/browserFeatures/isImageDecodeSupported'; import isWebAssemblySupported from '../features/browserFeatures/isWebAssemblySupported'; // Logger import { warn } from '../logger'; // Utilities import { assert } from '../utilities'; // Types import { ELoaderKey, IByDeviceTypeOptions, IBySupportedCompressedTextureOptions, ILoadItem, } from './types'; /** * Loader types and the extensions they handle * Allows the omission of the loader key for some generic extensions used on the web */ const LOADER_EXTENSIONS_MAP = new Map([ [ELoaderKey.Audio, { extensions: ['mp3', 'ogg', 'wav', 'flac'] }], [ELoaderKey.Font, { extensions: ['woff2', 'woff', 'ttf', 'otf', 'eot'] }], [ELoaderKey.Image, { extensions: ['jpeg', 'jpg', 'gif', 'png', 'webp'] }], [ELoaderKey.ImageBitmap, { extensions: ['jpeg', 'jpg', 'gif', 'png', 'webp'] }], [ELoaderKey.ImageCompressed, { extensions: ['ktx'] }], [ELoaderKey.JSON, { extensions: ['json'] }], [ELoaderKey.Text, { extensions: ['txt'] }], [ELoaderKey.Video, { extensions: ['webm', 'ogg', 'mp4'] }], [ELoaderKey.WebAssembly, { extensions: ['wasm', 'wat'] }], [ ELoaderKey.XML, { defaultMimeType: 'text/xml', extensions: ['xml', 'svg', 'html'], mimeType: { html: 'text/html', svg: 'image/svg+xml', xml: 'text/xml', }, }, ], ]); // Safari does not fire `canplaythrough` preventing it from resolving naturally. // A workaround is to not wait for the `canplaythrough` event but rather resolve early and hope for the best const IS_MEDIA_PRELOAD_SUPPORTED = !getBrowserType.isSafari; /** * Asynchronous asset preloader */ export class AssetLoader { /** * Load conditionally based on device type */ public static byDeviceType = (data: IByDeviceTypeOptions) => data.DESKTOP && getBrowserType.isDesktop ? data.DESKTOP : data.TABLET && getBrowserType.isTablet ? data.TABLET : data.MOBILE; /** * Load conditionally based on supported compressed texture */ public static bySupportedCompressedTexture = (data: IBySupportedCompressedTextureOptions) => { if (getWebGLFeatures) { return data.ASTC && getWebGLFeatures.extensions.compressedTextureASTCExtension ? data.ASTC : data.ETC && getWebGLFeatures.extensions.compressedTextureETCExtension ? data.S3TC : data.PVRTC && getWebGLFeatures.extensions.compressedTexturePVRTCExtension ? data.PVRTC : data.S3TC && getWebGLFeatures.extensions.compressedTextureS3TCExtension ? data.S3TC : data.FALLBACK; } else { return data.FALLBACK; } }; /** * DOMParser instance for the XML loader */ private static domParser = new DOMParser(); /** * Get a file extension from a full asset path * * @param path Path to asset */ private static getFileExtension = (path: string) => { const basename = path.split(/[\\/]/).pop(); if (!basename) { return ''; } const seperator = basename.lastIndexOf('.'); if (seperator < 1) { return ''; } return basename.slice(seperator + 1); }; /** * Retrieve mime type from extension * * @param loaderKey Loader key * @param extension extension */ private static getMimeType = (loaderKey: ELoaderKey, extension: string) => { const loader: any = LOADER_EXTENSIONS_MAP.get(loaderKey); return loader.mimeType[extension] || loader.defaultMimeType; }; /** * Retrieve loader key from extension (when the loader option isn't specified) * * @param path File path */ private static getLoaderByFileExtension = (path: string) => { const fileExtension = AssetLoader.getFileExtension(path); const loader = Array.from(LOADER_EXTENSIONS_MAP).find(type => type[1].extensions.includes(fileExtension) ); return loader ? loader[0] : ELoaderKey.ArrayBuffer; }; /** * Fetch wrapper for loading an item, to be processed by a specific loader afterwards * * @param item Item to fetch */ private static fetchItem = (item: ILoadItem) => fetch(item.src, item.options || {}); /** * Load an item and parse the Response as arrayBuffer * * @param item Item to load */ private static loadArrayBuffer = (item: ILoadItem) => AssetLoader.fetchItem(item) .then(response => response.arrayBuffer()) .catch(err => { warn(err); }); /** * Load an item and parse the Response as