├── src ├── index.js ├── browser-has-blob.js ├── emitter.js ├── stats.js ├── group.js └── loader.js ├── .gitignore ├── .travis.yml ├── bower.json ├── .gitattributes ├── test ├── files.js ├── assets.json ├── failure.spec.js ├── group.spec.js └── success.spec.js ├── LICENSE ├── package.json ├── examples ├── css │ ├── paraiso.light.css │ └── styles.css ├── js │ └── highlight.pack.js └── index.html ├── karma.conf.js ├── gulpfile.js ├── README.md ├── .jshintrc └── dist ├── assets-loader.min.js ├── assets-loader.js.map └── assets-loader.js /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assetsLoader = require('./group'); 4 | assetsLoader.stats = require('./stats'); 5 | 6 | module.exports = assetsLoader; 7 | -------------------------------------------------------------------------------- /src/browser-has-blob.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = (function() { 4 | try { 5 | return !!new Blob(); 6 | } catch (e) { 7 | return false; 8 | } 9 | }()); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | 3 | .* 4 | ~* 5 | !/.gitattributes 6 | !/.gitignore 7 | !/.gitinclude 8 | !/.jshintrc 9 | !/.travis.yml 10 | 11 | #Icon? 12 | ehthumbs.db 13 | Thumbs.db 14 | Desktop.ini 15 | user.properties 16 | 17 | bower_components/ 18 | node_modules/ 19 | temp/ 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: node_js 4 | node_js: 5 | - 6 6 | cache: 7 | directories: 8 | - node_modules 9 | addons: 10 | chrome: stable 11 | before_install: 12 | - export CHROME_BIN=chromium-browser 13 | - export DISPLAY=:99.0 14 | - sh -e /etc/init.d/xvfb start 15 | - sleep 3 16 | before_script: 17 | - npm i -g karma karma-cli 18 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assets-loader", 3 | "version": "0.5.0", 4 | "homepage": "https://github.com/ianmcgregor/assets-loader", 5 | "authors": [ 6 | "Ian McGregor " 7 | ], 8 | "description": "A simple batch assets loader.", 9 | "main": "dist/assets-loader.min.js", 10 | "moduleType": [ 11 | "node", 12 | "amd", 13 | "globals" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*", 18 | "node_modules", 19 | "bower_components", 20 | "test" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /src/emitter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var EventEmitter = require('events').EventEmitter; 4 | 5 | function Emitter() { 6 | EventEmitter.call(this); 7 | this.setMaxListeners(20); 8 | } 9 | 10 | Emitter.prototype = Object.create(EventEmitter.prototype); 11 | Emitter.prototype.constructor = Emitter; 12 | 13 | Emitter.prototype.off = function(type, listener) { 14 | if (listener) { 15 | return this.removeListener(type, listener); 16 | } 17 | if (type) { 18 | return this.removeAllListeners(type); 19 | } 20 | return this.removeAllListeners(); 21 | }; 22 | 23 | module.exports = Emitter; 24 | -------------------------------------------------------------------------------- /test/files.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assets = require('./assets.json'); 4 | 5 | var elAudio = document.createElement('audio'); 6 | var extAudio = (elAudio.canPlayType('audio/mpeg;') ? 'mp3' : 'ogg'); 7 | var elVideo = document.createElement('video'); 8 | var extVideo = (elVideo.canPlayType('video/webm;') ? 'webm' : 'mp4'); 9 | 10 | module.exports = { 11 | 'image': assets.image[0], 12 | 'svg': assets.svg, 13 | 'imageXHR': assets.image[1], 14 | 'audio': assets.audio[0][extAudio], 15 | 'video': assets.video[0][extVideo], 16 | 'json': assets.json[0], 17 | 'text': assets.text[0], 18 | 'images': [ 19 | { 20 | url: assets.image[2], 21 | type: 'jpg' 22 | }, 23 | { 24 | url: assets.image[3], 25 | type: 'jpg' 26 | } 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /test/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": [ 3 | "https://ianmcgregor.co/prototypes/images/cat_01.jpg", 4 | "https://ianmcgregor.co/prototypes/images/cat_02.jpg", 5 | "https://ianmcgregor.co/prototypes/images/cat_03.jpg", 6 | "https://ianmcgregor.co/prototypes/images/cat_04.jpg" 7 | ], 8 | "svg": "http://assets-loader.davidauthier.wearemd.com/flying-piggy-bank.svg", 9 | "audio": [ 10 | { 11 | "ogg": "https://ianmcgregor.co/prototypes/audio/music.ogg", 12 | "mp3": "https://ianmcgregor.co/prototypes/audio/music.mp3" 13 | } 14 | ], 15 | "video": [ 16 | { 17 | "mp4": "https://ianmcgregor.co/prototypes/video/counter.mp4", 18 | "webm": "https://ianmcgregor.co/prototypes/video/counter.webm" 19 | } 20 | ], 21 | "json": [ 22 | "https://ianmcgregor.co/prototypes/test/test.json" 23 | ], 24 | "text": [ 25 | "https://ianmcgregor.co/prototypes/video/tracking/tracking_01.txt" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ian McGregor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assets-loader", 3 | "version": "0.5.2", 4 | "description": "A simple batch assets loader.", 5 | "keywords": [ 6 | "loader", 7 | "assets", 8 | "lib", 9 | "utility", 10 | "util", 11 | "utilities", 12 | "javascript", 13 | "js" 14 | ], 15 | "author": "ianmcgregor", 16 | "license": "MIT", 17 | "main": "src/index.js", 18 | "scripts": { 19 | "start": "gulp", 20 | "build": "gulp release", 21 | "test": "karma start --single-run --browsers Chrome" 22 | }, 23 | "readmeFilename": "README.md", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/ianmcgregor/assets-loader" 27 | }, 28 | "devDependencies": { 29 | "browser-sync": "^2.7.13", 30 | "browserify": "^10.2.6", 31 | "bundle-collapser": "^1.2.0", 32 | "chai": "^3.0.0", 33 | "chalk": "^1.1.0", 34 | "events": "^1.0.2", 35 | "exorcist": "^0.4.0", 36 | "gulp": "^3.9.0", 37 | "gulp-derequire": "^2.1.0", 38 | "gulp-if": "^1.2.5", 39 | "gulp-jshint": "^1.11.2", 40 | "gulp-rename": "^1.2.2", 41 | "gulp-strip-debug": "^1.0.2", 42 | "gulp-uglify": "^1.2.0", 43 | "jshint-stylish": "^2.0.1", 44 | "karma": "^0.13.0", 45 | "karma-browserify": "^4.2.1", 46 | "karma-chai": "^0.1.0", 47 | "karma-chrome-launcher": "^0.2.0", 48 | "karma-firefox-launcher": "^0.1.6", 49 | "karma-mocha": "^0.2.0", 50 | "mocha": "^2.2.5", 51 | "vinyl-buffer": "^1.0.0", 52 | "vinyl-source-stream": "^1.1.0", 53 | "watchify": "^3.2.3" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/failure.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AssetsLoader = require('../src/index.js'); 4 | 5 | describe('asset loader', function() { 6 | 7 | describe('failure', function() { 8 | this.timeout(5000); 9 | 10 | var assetLoader = new AssetsLoader({ 11 | crossOrigin: 'anonymous' 12 | }); 13 | 14 | var complete = false; 15 | var loadProgress; 16 | var errorMessages = []; 17 | var badFiles = [ 18 | 'http://www.example.com/fooEl.jpg', 19 | {url: 'http://www.example.com/fooBlob.jpg', useImageXHR: true}, 20 | 'http://www.example.com/foo.ogg', 21 | 'http://www.example.com/foo.webm', 22 | 'http://www.example.com/foo.json' 23 | ]; 24 | 25 | badFiles.forEach(function(file) { 26 | assetLoader.add(file); 27 | }); 28 | 29 | beforeEach(function(done) { 30 | if (complete) { 31 | done(); 32 | return; 33 | } 34 | assetLoader.on('progress', function(progress) { 35 | loadProgress = progress; 36 | }) 37 | .on('error', function(error) { 38 | errorMessages.push(error); 39 | console.warn(error); 40 | }) 41 | .on('complete', function() { 42 | complete = true; 43 | done(); 44 | }) 45 | .start(); 46 | }); 47 | 48 | it ('should have finished loading', function() { 49 | expect(complete).equals(true); 50 | }); 51 | 52 | it ('should have caught errors', function() { 53 | expect(errorMessages.length).to.eql(badFiles.length); 54 | expect(errorMessages[0]).to.be.a('string'); 55 | }); 56 | 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /src/stats.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | mbs: 0, 5 | secs: 0, 6 | update: function(request, startTime, url, log) { 7 | var length; 8 | var headers = request.getAllResponseHeaders(); 9 | if (headers) { 10 | var match = headers.match(/content-length: (\d+)/i); 11 | if (match && match.length) { 12 | length = match[1]; 13 | } 14 | } 15 | // var length = request.getResponseHeader('Content-Length'); 16 | if (length) { 17 | length = parseInt(length, 10); 18 | var mbs = length / 1024 / 1024; 19 | var secs = (Date.now() - startTime) / 1000; 20 | this.secs += secs; 21 | this.mbs += mbs; 22 | if (log) { 23 | this.log(url, mbs, secs); 24 | } 25 | } else if(log) { 26 | console.warn.call(console, 'Can\'t get Content-Length:', url); 27 | } 28 | }, 29 | log: function(url, mbs, secs) { 30 | if (url) { 31 | var file = 'File loaded: ' + 32 | url.substr(url.lastIndexOf('/') + 1) + 33 | ' size:' + mbs.toFixed(2) + 'mb' + 34 | ' time:' + secs.toFixed(2) + 's' + 35 | ' speed:' + (mbs / secs).toFixed(2) + 'mbps'; 36 | 37 | console.log.call(console, file); 38 | } 39 | var total = 'Total loaded: ' + this.mbs.toFixed(2) + 'mb' + 40 | ' time:' + this.secs.toFixed(2) + 's' + 41 | ' speed:' + this.getMbps().toFixed(2) + 'mbps'; 42 | console.log.call(console, total); 43 | }, 44 | getMbps: function() { 45 | return this.mbs / this.secs; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /examples/css/paraiso.light.css: -------------------------------------------------------------------------------- 1 | /* 2 | Paraíso (light) 3 | Created by Jan T. Sott (http://github.com/idleberg) 4 | Inspired by the art of Rubens LP (http://www.rubenslp.com.br) 5 | */ 6 | 7 | /* Paraíso Comment */ 8 | .hljs-comment, 9 | .hljs-title { 10 | color: #776e71; 11 | } 12 | 13 | /* Paraíso Red */ 14 | .hljs-variable, 15 | .hljs-attribute, 16 | .hljs-tag, 17 | .hljs-regexp, 18 | .ruby .hljs-constant, 19 | .xml .hljs-tag .hljs-title, 20 | .xml .hljs-pi, 21 | .xml .hljs-doctype, 22 | .html .hljs-doctype, 23 | .css .hljs-id, 24 | .css .hljs-class, 25 | .css .hljs-pseudo { 26 | color: #ef6155; 27 | } 28 | 29 | /* Paraíso Orange */ 30 | .hljs-number, 31 | .hljs-preprocessor, 32 | .hljs-built_in, 33 | .hljs-literal, 34 | .hljs-params, 35 | .hljs-constant { 36 | color: #f99b15; 37 | } 38 | 39 | /* Paraíso Yellow */ 40 | .ruby .hljs-class .hljs-title, 41 | .css .hljs-rules .hljs-attribute { 42 | color: #fec418; 43 | } 44 | 45 | /* Paraíso Green */ 46 | .hljs-string, 47 | .hljs-value, 48 | .hljs-inheritance, 49 | .hljs-header, 50 | .ruby .hljs-symbol, 51 | .xml .hljs-cdata { 52 | color: #48b685; 53 | } 54 | 55 | /* Paraíso Aqua */ 56 | .css .hljs-hexcolor { 57 | color: #5bc4bf; 58 | } 59 | 60 | /* Paraíso Blue */ 61 | .hljs-function, 62 | .python .hljs-decorator, 63 | .python .hljs-title, 64 | .ruby .hljs-function .hljs-title, 65 | .ruby .hljs-title .hljs-keyword, 66 | .perl .hljs-sub, 67 | .javascript .hljs-title, 68 | .coffeescript .hljs-title { 69 | color: #06b6ef; 70 | } 71 | 72 | /* Paraíso Purple */ 73 | .hljs-keyword, 74 | .javascript .hljs-function { 75 | color: #815ba4; 76 | } 77 | 78 | .hljs { 79 | display: block; 80 | overflow-x: auto; 81 | background: #e7e9db; 82 | color: #4f424c; 83 | padding: 0.5em; 84 | -webkit-text-size-adjust: none; 85 | } 86 | 87 | .coffeescript .javascript, 88 | .javascript .xml, 89 | .tex .hljs-formula, 90 | .xml .javascript, 91 | .xml .vbscript, 92 | .xml .css, 93 | .xml .hljs-cdata { 94 | opacity: 0.5; 95 | } 96 | -------------------------------------------------------------------------------- /test/group.spec.js: -------------------------------------------------------------------------------- 1 | const assetsLoader = require('../src/index.js'); 2 | const files = require('./files.js'); 3 | 4 | describe('asset loader', () => { 5 | 6 | describe('group', () => { 7 | 8 | let complete = false; 9 | let loadProgress = 0; 10 | let groupA = null; 11 | let groupB = null; 12 | 13 | const loader = assetsLoader() 14 | .add({ 15 | id: 'groupA', 16 | assets: files.images 17 | }) 18 | .add({ 19 | id: 'groupB', 20 | assets: [files.json] 21 | }); 22 | 23 | beforeEach(function(done) { 24 | if (complete) { 25 | done(); 26 | return; 27 | } 28 | loader 29 | .on('progress', function(progress) { 30 | loadProgress = progress; 31 | }) 32 | .on('complete', () => { 33 | complete = true; 34 | groupA = loader.get('groupA'); 35 | groupB = loader.get('groupB'); 36 | done(); 37 | }) 38 | .on('error', function(error) { 39 | console.log(error); 40 | }) 41 | .start(); 42 | }); 43 | 44 | it('should have finished loading', () => { 45 | expect(complete).equals(true); 46 | expect(loadProgress).to.eql(1); 47 | }); 48 | 49 | it('should have groupA', () => { 50 | expect(groupA).to.exist; 51 | expect(groupA.get()).to.have.lengthOf(2); 52 | expect(groupA.get()[0]).to.have.property('type', 'jpg'); 53 | }); 54 | 55 | it('should have groupB', () => { 56 | expect(groupB).to.exist; 57 | expect(groupB.get()).to.have.lengthOf(1); 58 | expect(groupB.get()[0]).to.have.property('type', 'json'); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // base path, that will be used to resolve files and exclude 7 | basePath: '', 8 | 9 | client: { 10 | mocha: { 11 | timeout: 20000 12 | } 13 | }, 14 | 15 | plugins: [ 16 | 'karma-mocha', 17 | 'karma-chai', 18 | 'karma-browserify', 19 | 'karma-chrome-launcher', 20 | 'karma-firefox-launcher' 21 | //,'karma-ievms' 22 | //,'karma-ios-launcher' 23 | ], 24 | 25 | // frameworks to use 26 | frameworks: ['browserify', 'mocha', 'chai'], 27 | 28 | // list of files / patterns to load in the browser 29 | files: [ 30 | 'test/**/*.spec.js' 31 | ], 32 | 33 | 34 | // list of files to exclude 35 | exclude: [ 36 | 37 | ], 38 | 39 | // Browserify config (all optional) 40 | browserify: { 41 | // extensions: ['.coffee'], 42 | // ignore: [], 43 | // transform: ['coffeeify'], 44 | // debug: true, 45 | // noParse: ['jquery'], 46 | // watch: true 47 | }, 48 | 49 | // Add browserify to preprocessors 50 | preprocessors: {'test/**/*.js': ['browserify']}, 51 | 52 | 53 | // test results reporter to use 54 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 55 | reporters: ['progress'], 56 | 57 | 58 | // web server port 59 | port: 9876, 60 | 61 | 62 | // enable / disable colors in the output (reporters and logs) 63 | colors: true, 64 | 65 | 66 | // level of logging 67 | /* possible values: 68 | config.LOG_DISABLE 69 | config.LOG_ERROR 70 | config.LOG_WARN 71 | config.LOG_INFO 72 | config.LOG_DEBUG*/ 73 | logLevel: config.LOG_INFO, 74 | 75 | 76 | // enable / disable watching file and executing tests whenever any file changes 77 | autoWatch: true, 78 | 79 | 80 | // Start these browsers, currently available: 81 | // - Chrome 82 | // - ChromeCanary 83 | // - Firefox 84 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 85 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 86 | // - PhantomJS 87 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 88 | browsers: [ 89 | //'iOS', 90 | 'Chrome', 91 | 'Firefox' 92 | //,'IE11 - Win7' 93 | //,'IE10 - Win7' 94 | ], 95 | 96 | 97 | // If browser does not capture in given timeout [ms], kill it 98 | captureTimeout: 60000, 99 | 100 | 101 | // Continuous Integration mode 102 | // if true, it capture browsers, run tests and exit 103 | singleRun: false 104 | }); 105 | }; 106 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var browserify = require('browserify'), 4 | browserSync = require('browser-sync'), 5 | buffer = require('vinyl-buffer'), 6 | chalk = require('chalk'), 7 | collapse = require('bundle-collapser/plugin'), 8 | derequire = require('gulp-derequire'), 9 | exorcist = require('exorcist'), 10 | gulp = require('gulp'), 11 | jshint = require('gulp-jshint'), 12 | rename = require('gulp-rename'), 13 | source = require('vinyl-source-stream'), 14 | strip = require('gulp-strip-debug'), 15 | uglify = require('gulp-uglify'), 16 | watchify = require('watchify'); 17 | 18 | var standaloneName = 'assetsLoader', 19 | entryFileName = 'index.js', 20 | bundleFileName = 'assets-loader.js'; 21 | 22 | // log 23 | function logError(msg) { 24 | console.log(chalk.bold.red('[ERROR] ' + msg.toString())); 25 | } 26 | 27 | // bundler 28 | var bundler = watchify(browserify({ 29 | entries: ['./src/' + entryFileName], 30 | standalone: standaloneName, 31 | debug: true 32 | }, watchify.args)); 33 | 34 | function bundle() { 35 | return bundler 36 | .bundle() 37 | .pipe(exorcist('./dist/' + bundleFileName + '.map')) 38 | .on('error', logError) 39 | .pipe(source(bundleFileName)) 40 | .pipe(buffer()) 41 | .pipe(derequire()) 42 | .pipe(gulp.dest('./dist/')) 43 | .pipe(rename({ extname: '.min.js' })) 44 | .pipe(strip()) 45 | .pipe(uglify()) 46 | .pipe(gulp.dest('./dist/')); 47 | } 48 | 49 | bundler.on('update', bundle); // on any dep update, runs the bundler 50 | gulp.task('bundle', ['jshint'], bundle); 51 | 52 | // release bundle with extra compression (can't get collapse to work with watchify) 53 | function bundleRelease(minify) { 54 | var bundler = browserify({ 55 | entries: ['./src/' + entryFileName], 56 | standalone: standaloneName, 57 | debug: !minify 58 | }); 59 | 60 | if(minify) { 61 | bundler = bundler.plugin(collapse); 62 | } 63 | 64 | var stream = bundler.bundle() 65 | .on('error', logError); 66 | 67 | if(!minify) { 68 | stream = stream.pipe(exorcist('./dist/' + bundleFileName + '.map')); 69 | } 70 | 71 | stream = stream.pipe(source(bundleFileName)) 72 | .pipe(buffer()) 73 | .pipe(derequire()) 74 | .pipe(strip()); 75 | 76 | if(minify) { 77 | return stream.pipe(rename({extname: '.min.js'})) 78 | .pipe(uglify()) 79 | .pipe(gulp.dest('./dist/')); 80 | } else { 81 | return stream.pipe(gulp.dest('./dist/')); 82 | } 83 | } 84 | 85 | gulp.task('release', function() { 86 | bundleRelease(true); 87 | bundleRelease(false); 88 | }); 89 | 90 | // connect browsers 91 | gulp.task('connect', function() { 92 | browserSync.init({ 93 | server: { 94 | baseDir: ['./', 'examples'] 95 | }, 96 | files: [ 97 | 'dist/*', 98 | 'examples/**/*' 99 | ], 100 | reloadDebounce: 500 101 | }); 102 | }); 103 | 104 | // reload browsers 105 | gulp.task('reload', function() { 106 | browserSync.reload(); 107 | }); 108 | 109 | // js hint 110 | gulp.task('jshint', function() { 111 | return gulp.src([ 112 | './gulpfile.js', 113 | 'src/**/*.js', 114 | 'test/**/*.js', 115 | 'examples/**/*.js', 116 | '!examples/js/highlight.pack.js' 117 | ]) 118 | .pipe(jshint()) 119 | .pipe(jshint.reporter('jshint-stylish')); 120 | }); 121 | 122 | // watch 123 | gulp.task('watch', function() { 124 | gulp.watch('test/**/*.js', ['jshint']); 125 | gulp.watch('examples/**/*.js', ['jshint']); 126 | }); 127 | 128 | // default 129 | gulp.task('default', ['connect', 'watch', 'bundle']); 130 | -------------------------------------------------------------------------------- /test/success.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AssetsLoader = require('../src/index.js'); 4 | var files = require('./files.js'); 5 | 6 | describe('asset loader', function() { 7 | 8 | describe('success', function() { 9 | this.timeout(5000); 10 | 11 | var complete = false; 12 | var loadProgress; 13 | 14 | var loader = new AssetsLoader({ 15 | crossOrigin: 'anonymous' 16 | }) 17 | .add({ 18 | url: files.image, 19 | type: 'jpg' 20 | }) 21 | .add({ 22 | url: files.svg, 23 | type: 'svg' 24 | }) 25 | .add({ 26 | url: files.imageXHR, 27 | type: 'jpg', 28 | useImageXHR: true 29 | }) 30 | .add(files.audio) 31 | .add(files.video) 32 | .add({ 33 | url: files.json, 34 | type: 'json' 35 | }) 36 | .add(files.text) 37 | .add(files.images); 38 | 39 | beforeEach(function(done) { 40 | if (complete) { 41 | done(); 42 | return; 43 | } 44 | loader.on('progress', function(progress) { 45 | loadProgress = progress; 46 | }) 47 | .on('complete', function() { 48 | complete = true; 49 | 50 | // manual tests to view on karma debug page: 51 | document.body.appendChild(loader.get(files.image)); 52 | document.body.appendChild(loader.get(files.svg)); 53 | document.body.appendChild(loader.get(files.imageXHR)); 54 | document.body.insertAdjacentHTML('beforeend', JSON.stringify(loader.get(files.json))); 55 | loader.get(files.audio).setAttribute('controls', 'controls'); 56 | document.body.appendChild(loader.get(files.audio)); 57 | loader.get(files.audio).play(); 58 | loader.get(files.video).setAttribute('controls', 'controls'); 59 | document.body.appendChild(loader.get(files.video)); 60 | files.images.forEach(function(img) { 61 | document.body.appendChild(loader.get(img.url)); 62 | }); 63 | 64 | done(); 65 | }) 66 | .on('error', function(error) { 67 | console.log(error); 68 | }) 69 | .start(); 70 | }); 71 | 72 | it ('should have finished loading', function() { 73 | expect(complete).equals(true); 74 | expect(loadProgress).to.eql(1); 75 | }); 76 | 77 | it ('should have loaded image element', function() { 78 | expect(loader.get(files.image)).to.exist; 79 | expect(loader.get(files.image).tagName).to.eql('IMG'); 80 | }); 81 | 82 | it ('should have a SVG element', function() { 83 | expect(loader.get(files.svg)).to.exist; 84 | expect(loader.get(files.svg).tagName).to.eql('IMG'); 85 | }); 86 | 87 | it ('should have loaded image blob', function() { 88 | expect(loader.get(files.imageXHR)).to.exist; 89 | expect(loader.get(files.imageXHR).tagName).to.eql('IMG'); 90 | }); 91 | 92 | it ('should have loaded audio', function() { 93 | expect(loader.get(files.audio)).to.exist; 94 | expect(loader.get(files.audio).tagName).to.eql('AUDIO'); 95 | }); 96 | 97 | it ('should have loaded video', function() { 98 | expect(loader.get(files.video)).to.exist; 99 | expect(loader.get(files.video).tagName).to.eql('VIDEO'); 100 | }); 101 | 102 | it ('should have loaded json', function() { 103 | expect(loader.get(files.json)).to.exist; 104 | expect(loader.get(files.json)).to.be.an('object'); 105 | expect(loader.get(files.json).name).to.be.a('string'); 106 | }); 107 | 108 | it ('should have recorded stats', function() { 109 | expect(AssetsLoader.stats).to.be.an('object'); 110 | expect(AssetsLoader.stats.getMbps()).to.be.a('number'); 111 | }); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # assets-loader 2 | 3 | [![NPM version](https://badge.fury.io/js/assets-loader.svg)](http://badge.fury.io/js/assets-loader) [![Bower version](https://badge.fury.io/bo/assets-loader.svg)](http://badge.fury.io/bo/assets-loader) [![Build Status](https://secure.travis-ci.org/ianmcgregor/assets-loader.png)](https://travis-ci.org/ianmcgregor/assets-loader) 4 | 5 | A simple batch assets loader. 6 | 7 | 8 | 9 | ### Installation 10 | 11 | npm: 12 | ``` 13 | npm install assets-loader --save-dev 14 | ``` 15 | bower: 16 | ``` 17 | bower install assets-loader --save-dev 18 | ``` 19 | 20 | ### Usage 21 | 22 | ```javascript 23 | var assetsLoader = require('assets-loader'); 24 | 25 | // load some assets: 26 | 27 | var loader = assetsLoader({ 28 | assets: [ 29 | // image 30 | '/images/picture.png', 31 | // image with crossorigin 32 | { url: '/images/picture.jpg', crossOrigin: 'anonymous' }, 33 | // image without extension 34 | { url: 'http://lorempixel.com/100/100', type: 'jpg' }, 35 | // image as blob 36 | { url: '/images/picture.webp', blob: true }, 37 | // specify id for retrieval 38 | { id: 'picture', url: '/images/picture.jpg' }, 39 | // json 40 | 'data.json', 41 | { url: 'data.json' }, 42 | { url: '/endpoint', type: 'json' }, 43 | // video 44 | 'video.webm', 45 | { url: 'video.webm' }, 46 | { url: 'video.mp4', blob: true }, 47 | // audio 48 | 'audio.ogg', 49 | { url: 'audio.ogg', blob: true }, 50 | { url: 'audio.mp3', webAudioContext: audioContext }, 51 | // binary / arraybuffer 52 | 'binary_file.bin', 53 | { url: 'binary_file', type: 'bin' }, 54 | // text 55 | 'text_file.txt', 56 | { url: 'text_file', type: 'text' } 57 | ] 58 | }) 59 | .on('error', function(error) { 60 | console.error(error); 61 | }) 62 | .on('progress', function(progress) { 63 | console.log((progress * 100).toFixed() + '%'); 64 | }) 65 | .on('complete', function(assets) { 66 | assets.forEach(function(asset) { 67 | console.log(asset); 68 | }); 69 | // get by id from loader instance 70 | console.log(loader.get('picture')); 71 | }) 72 | .start(); 73 | 74 | // add assets in separate steps 75 | 76 | var loader = assetsLoader() 77 | .add('audio.mp3') 78 | .add('picture.jpg') 79 | .add([ 80 | 'a.png', 81 | 'b.png' 82 | ]) 83 | .add({ 84 | id: 'video', 85 | url: 'video.webm' 86 | }) 87 | .add({ 88 | id: 'sounds', 89 | assets: [ 90 | { id: 'a', url: 'a.mp3' }, 91 | { id: 'b', url: 'b.mp3' } 92 | ] 93 | }) 94 | .on('complete', function(assets) { 95 | console.log(assets); 96 | console.log(loader.get('video')); 97 | console.log(loader.get('sounds')); 98 | }) 99 | .start(); 100 | 101 | // configure values for every file 102 | 103 | var loader = assetsLoader({ 104 | blob: true, // only works if browser supports 105 | crossOrigin: 'anonymous', 106 | webAudioContext: audioContext, 107 | assets: [ 108 | { id: 'a', url: 'a.mp3' }, 109 | { id: 'b', url: 'b.jpg' }, 110 | // override blob setting for this file 111 | { id: 'c', url: 'c.jpg', blob: false } 112 | ] 113 | }); 114 | 115 | // destroy 116 | 117 | loader.destroy(); 118 | loader.getLoader('groupId').destroy(); 119 | 120 | // stats 121 | 122 | console.log(assetsLoader.stats.getMbps()); // e.g. 3.2 123 | assetsLoader.stats.log(); // e.g. Total loaded: 2.00mb time: 2.00s speed: 1.00mbps 124 | ``` 125 | 126 | ### Create an `assets.json` file 127 | Sometimes you may need to load a lot of assets. A simple solution for that is to generate an `assets.json` file listing all your assets. Here is a bash script to do that: 128 | 129 | ```bash 130 | #!/bin/bash 131 | 132 | # Assign found results to an array 133 | # Source: https://stackoverflow.com/a/23357277/616095 134 | assets=() 135 | while IFS=read -r -d $'\0'; do assets+=("${REPLY//static\//}") 136 | 137 | # Filter results (excluding static/fonts folder) 138 | # Source: http://www.liamdelahunty.com/tips/linux_find_exclude_multiple_directories.php 139 | done < <(find static \( -path static/fonts -o -name ".*" \) -prune -o -type f -print0) 140 | 141 | # Format an array to JSON (require https://github.com/stedolan/jq) 142 | # Source: https://stackoverflow.com/a/26809318/616095 143 | printf '%s\n' "${assets[@]}" | jq -R . | jq -s . > dest/assets.json 144 | ``` 145 | 146 | This script assumed that your assets are located in a `static/` folder and write the result to `dest/assets.json`. After running the script you just have to require your JSON file: 147 | 148 | ```javascript 149 | new assetsLoader({ 150 | assets: require('dest/assets.json') 151 | }) 152 | ``` 153 | 154 | ### Dev setup 155 | 156 | To install dependencies: 157 | 158 | ``` 159 | $ npm install 160 | ``` 161 | 162 | To run tests: 163 | 164 | ``` 165 | $ npm install -g karma-cli 166 | $ karma start 167 | ``` 168 | -------------------------------------------------------------------------------- /examples/css/styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /*--color-main: rgba(255, 0, 255, 0.6); 3 | --color-dark: rgba(255, 0, 255, 1);*/ 4 | } 5 | 6 | /*! Libraries */ 7 | 8 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */ 9 | 10 | html { 11 | -ms-text-size-adjust: 100%; 12 | -webkit-text-size-adjust: 100%; 13 | } 14 | 15 | article, 16 | aside, 17 | details, 18 | figcaption, 19 | figure, 20 | footer, 21 | header, 22 | hgroup, 23 | main, 24 | menu, 25 | nav, 26 | section, 27 | summary { 28 | display: block; 29 | } 30 | 31 | audio, 32 | canvas, 33 | progress, 34 | video { 35 | display: inline-block; 36 | vertical-align: baseline; 37 | } 38 | 39 | audio:not([controls]) { 40 | display: none; 41 | height: 0; 42 | } 43 | 44 | [hidden], 45 | template { 46 | display: none; 47 | } 48 | 49 | a { 50 | background-color: transparent; 51 | } 52 | 53 | a:active, 54 | a:hover { 55 | outline: 0; 56 | } 57 | 58 | abbr[title] { 59 | border-bottom: 1px dotted; 60 | } 61 | 62 | b, 63 | strong { 64 | font-weight: 700; 65 | } 66 | 67 | dfn { 68 | font-style: italic; 69 | } 70 | 71 | h1 { 72 | font-size: 2em; 73 | } 74 | 75 | mark { 76 | background: #ff0; 77 | color: #000; 78 | } 79 | 80 | small { 81 | font-size: 80%; 82 | } 83 | 84 | sub, 85 | sup { 86 | font-size: 75%; 87 | line-height: 0; 88 | position: relative; 89 | vertical-align: baseline; 90 | } 91 | 92 | sup { 93 | top: -.5em; 94 | } 95 | 96 | sub { 97 | bottom: -.25em; 98 | } 99 | 100 | img { 101 | border: 0; 102 | } 103 | 104 | svg:not(:root) { 105 | overflow: hidden; 106 | } 107 | 108 | hr { 109 | box-sizing: content-box; 110 | height: 0; 111 | } 112 | 113 | pre { 114 | overflow: auto; 115 | } 116 | 117 | code, 118 | kbd, 119 | pre, 120 | samp { 121 | font-family: monospace,monospace; 122 | font-size: 1em; 123 | } 124 | 125 | button, 126 | input, 127 | optgroup, 128 | select, 129 | textarea { 130 | color: inherit; 131 | font: inherit; 132 | margin: 0; 133 | } 134 | 135 | button { 136 | overflow: visible; 137 | } 138 | 139 | button, 140 | select { 141 | text-transform: none; 142 | } 143 | 144 | button, 145 | html input[type=button], 146 | input[type=reset], 147 | input[type=submit] { 148 | -webkit-appearance: button; 149 | cursor: pointer; 150 | } 151 | 152 | button[disabled], 153 | html input[disabled] { 154 | cursor: default; 155 | } 156 | 157 | button::-moz-focus-inner, 158 | input::-moz-focus-inner { 159 | border: 0; 160 | padding: 0; 161 | } 162 | 163 | input { 164 | line-height: normal; 165 | } 166 | 167 | input[type=checkbox], 168 | input[type=radio] { 169 | box-sizing: border-box; 170 | padding: 0; 171 | } 172 | 173 | input[type=number]::-webkit-inner-spin-button, 174 | input[type=number]::-webkit-outer-spin-button { 175 | height: auto; 176 | } 177 | 178 | input[type=search] { 179 | -webkit-appearance: textfield; 180 | box-sizing: content-box; 181 | } 182 | 183 | input[type=search]::-webkit-search-cancel-button, 184 | input[type=search]::-webkit-search-decoration { 185 | -webkit-appearance: none; 186 | } 187 | 188 | legend { 189 | border: 0; 190 | padding: 0; 191 | } 192 | 193 | textarea { 194 | overflow: auto; 195 | } 196 | 197 | optgroup { 198 | font-weight: 700; 199 | } 200 | 201 | table { 202 | border-collapse: collapse; 203 | border-spacing: 0; 204 | } 205 | 206 | td, 207 | th { 208 | padding: 0; 209 | } 210 | 211 | 212 | 213 | html { 214 | background: inherit; 215 | color: inherit; 216 | } 217 | 218 | a { 219 | color: #069; 220 | text-decoration: none; 221 | } 222 | 223 | a:active, 224 | a:focus, 225 | a:hover { 226 | color: #069; 227 | text-decoration: underline; 228 | } 229 | 230 | blockquote, 231 | dd, 232 | dl, 233 | figure, 234 | h1, 235 | h2, 236 | h3, 237 | h4, 238 | h5, 239 | h6, 240 | p, 241 | pre { 242 | margin: 0; 243 | } 244 | 245 | button { 246 | background: 0 0; 247 | border: 0; 248 | padding: 0; 249 | } 250 | 251 | button:focus { 252 | outline: dotted 1px; 253 | outline: -webkit-focus-ring-color auto 5px; 254 | } 255 | 256 | fieldset { 257 | border: 0; 258 | margin: 0; 259 | padding: 0; 260 | } 261 | 262 | iframe { 263 | border: 0; 264 | } 265 | 266 | ol, 267 | ul { 268 | list-style: none; 269 | margin: 0; 270 | padding: 0; 271 | } 272 | 273 | [tabindex="-1"]:focus { 274 | outline: 0!important; 275 | } 276 | 277 | /*! Sections */ 278 | 279 | html { 280 | height: 100%; 281 | } 282 | 283 | body { 284 | background-color: #FFF; 285 | color: #000; 286 | font-family: Helvetica, sans-serif; 287 | font-size: 16px; 288 | height: 100%; 289 | line-height: 1.5; 290 | margin: 0; 291 | } 292 | 293 | h1, 294 | h2, 295 | h3, 296 | button { 297 | color: #303030; 298 | font-weight: 400; 299 | margin: .5em 0; 300 | } 301 | 302 | h1 { 303 | font-size: 48px; 304 | margin: 0; 305 | padding-bottom: 20px; 306 | } 307 | 308 | h2 { 309 | font-size: 32px; 310 | } 311 | 312 | h3 { 313 | 314 | } 315 | 316 | header { 317 | text-align: center; 318 | width: 100%; 319 | } 320 | 321 | section { 322 | 323 | } 324 | 325 | pre { 326 | padding: 50px 0; 327 | width: 100%; 328 | } 329 | 330 | code { 331 | border-radius: 8px; 332 | margin: 0 auto; 333 | max-width: 600px; 334 | min-width: 300px; 335 | width: 80%; 336 | } 337 | 338 | @media (max-width: 400px) { 339 | code { 340 | font-size: 60%; 341 | } 342 | } 343 | 344 | img { 345 | display: block; 346 | } 347 | 348 | .Container { 349 | -webkit-align-items: center; 350 | align-items: center; 351 | display: -webkit-flex; 352 | display: flex; 353 | -webkit-flex-wrap: wrap; 354 | flex-wrap: wrap; 355 | font-size: 60px; 356 | -webkit-justify-content: center; 357 | justify-content: center; 358 | margin: 0 auto; 359 | max-width: 1000px; 360 | width: 90%; 361 | } 362 | 363 | button { 364 | border: 1px solid gray; 365 | border-radius: 4px; 366 | font-size: 32px; 367 | padding: 10px; 368 | } 369 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "indent" : 4, // {int} Number of spaces to use for indentation 16 | "latedef" : false, // true: Require variables/functions to be defined before being used 17 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 18 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 19 | "noempty" : true, // true: Prohibit use of empty blocks 20 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 21 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 22 | "plusplus" : false, // true: Prohibit use of `++` & `--` 23 | "quotmark" : false, // Quotation mark consistency: 24 | // false : do nothing (default) 25 | // true : ensure whatever is used is consistent 26 | // "single" : require single quotes 27 | // "double" : require double quotes 28 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 29 | "unused" : true, // true: Require all defined variables be used 30 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 31 | "maxparams" : false, // {int} Max number of formal params allowed per function 32 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 33 | "maxstatements" : false, // {int} Max number statements per function 34 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 35 | "maxlen" : false, // {int} Max number of characters per line 36 | 37 | // Relaxing 38 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 39 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 40 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 41 | "eqnull" : false, // true: Tolerate use of `== null` 42 | "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) 43 | "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) 44 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 45 | // (ex: `for each`, multiple try/catch, function expression…) 46 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 47 | "expr" : true, // true: Tolerate `ExpressionStatement` as Programs 48 | "funcscope" : false, // true: Tolerate defining variables inside control statements 49 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 50 | "iterator" : false, // true: Tolerate using the `__iterator__` property 51 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 52 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 53 | "laxcomma" : false, // true: Tolerate comma-first style coding 54 | "loopfunc" : false, // true: Tolerate functions being defined in loops 55 | "multistr" : false, // true: Tolerate multi-line strings 56 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 57 | "notypeof" : false, // true: Tolerate invalid typeof operator values 58 | "proto" : false, // true: Tolerate using the `__proto__` property 59 | "scripturl" : false, // true: Tolerate script-targeted URLs 60 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 61 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 62 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 63 | "validthis" : false, // true: Tolerate using this in a non-constructor function 64 | 65 | // Environments 66 | "browser" : true, // Web Browser (window, document, etc) 67 | "browserify" : true, // Browserify (node.js code in the browser) 68 | "couch" : false, // CouchDB 69 | "devel" : true, // Development/debugging (alert, confirm, etc) 70 | "dojo" : false, // Dojo Toolkit 71 | "jasmine" : false, // Jasmine 72 | "jquery" : false, // jQuery 73 | "mocha" : true, // Mocha 74 | "mootools" : false, // MooTools 75 | "node" : false, // Node.js 76 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 77 | "prototypejs" : false, // Prototype and Scriptaculous 78 | "qunit" : false, // QUnit 79 | "rhino" : false, // Rhino 80 | "shelljs" : false, // ShellJS 81 | "worker" : false, // Web Workers 82 | "wsh" : false, // Windows Scripting Host 83 | "yui" : false, // Yahoo User Interface 84 | 85 | // Custom Globals (additional predefined global variables) 86 | "globals" : { 87 | "expect": false 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/group.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Emitter = require('./emitter.js'); 4 | var createLoader = require('./loader'); 5 | var autoId = 0; 6 | 7 | module.exports = function createGroup(config) { 8 | var group; 9 | var map = {}; 10 | var assets = []; 11 | var queue = []; 12 | var numLoaded = 0; 13 | var numTotal = 0; 14 | var loaders = {}; 15 | 16 | var add = function(options) { 17 | // console.debug('add', options); 18 | if (Array.isArray(options)) { 19 | options.forEach(add); 20 | return group; 21 | } 22 | var isGroup = !!options.assets && Array.isArray(options.assets); 23 | // console.debug('isGroup', isGroup); 24 | var loader; 25 | if (isGroup) { 26 | loader = createGroup(configure(options, config)); 27 | } else { 28 | loader = createLoader(configure(options, config)); 29 | } 30 | loader.once('destroy', destroyHandler); 31 | queue.push(loader); 32 | loaders[loader.id] = loader; 33 | return group; 34 | }; 35 | 36 | var get = function(id) { 37 | if (!arguments.length) { 38 | return assets; 39 | } 40 | if (map[id]) { 41 | return map[id]; 42 | } 43 | return loaders[id]; 44 | }; 45 | 46 | var find = function(id) { 47 | if (get(id)) { 48 | return get(id); 49 | } 50 | var found = null; 51 | Object.keys(loaders).some(function(key) { 52 | found = loaders[key].find && loaders[key].find(id); 53 | return !!found; 54 | }); 55 | return found; 56 | }; 57 | 58 | var getExtension = function(url) { 59 | return url && url.split('?')[0].split('.').pop().toLowerCase(); 60 | }; 61 | 62 | var configure = function(options, defaults) { 63 | if (typeof options === 'string') { 64 | var url = options; 65 | options = { 66 | url: url 67 | }; 68 | } 69 | 70 | if (options.isTouchLocked === undefined) { 71 | options.isTouchLocked = defaults.isTouchLocked; 72 | } 73 | 74 | if (options.blob === undefined) { 75 | options.blob = defaults.blob; 76 | } 77 | 78 | if (options.basePath === undefined) { 79 | options.basePath = defaults.basePath; 80 | } 81 | 82 | options.id = options.id || options.url || String(++autoId); 83 | options.type = options.type || getExtension(options.url); 84 | options.crossOrigin = options.crossOrigin || defaults.crossOrigin; 85 | options.webAudioContext = options.webAudioContext || defaults.webAudioContext; 86 | options.log = defaults.log; 87 | 88 | return options; 89 | }; 90 | 91 | var start = function() { 92 | numTotal = queue.length; 93 | 94 | queue.forEach(function(loader) { 95 | loader 96 | .on('progress', progressHandler) 97 | .once('complete', completeHandler) 98 | .once('error', errorHandler) 99 | .start(); 100 | }); 101 | 102 | queue = []; 103 | 104 | return group; 105 | }; 106 | 107 | var progressHandler = function(progress) { 108 | var loaded = numLoaded + progress; 109 | group.emit('progress', loaded / numTotal); 110 | }; 111 | 112 | var completeHandler = function(asset, id, type) { 113 | if (Array.isArray(asset)) { 114 | asset = { id: id, file: asset, type: type }; 115 | } 116 | numLoaded++; 117 | group.emit('progress', numLoaded / numTotal); 118 | map[asset.id] = asset.file; 119 | assets.push(asset); 120 | group.emit('childcomplete', asset); 121 | checkComplete(); 122 | }; 123 | 124 | var errorHandler = function(err) { 125 | numTotal--; 126 | if (group.listeners('error').length) { 127 | group.emit('error', err); 128 | } else { 129 | console.error(err); 130 | } 131 | checkComplete(); 132 | }; 133 | 134 | var destroyHandler = function(id) { 135 | loaders[id] = null; 136 | delete loaders[id]; 137 | 138 | map[id] = null; 139 | delete map[id]; 140 | 141 | assets.some(function(asset, i) { 142 | if (asset.id === id) { 143 | assets.splice(i, 1); 144 | return true; 145 | } 146 | }); 147 | }; 148 | 149 | var checkComplete = function() { 150 | if (numLoaded >= numTotal) { 151 | group.emit('complete', assets, map, config.id, 'group'); 152 | } 153 | }; 154 | 155 | var destroy = function() { 156 | while (queue.length) { 157 | queue.pop().destroy(); 158 | } 159 | group.off('error'); 160 | group.off('progress'); 161 | group.off('complete'); 162 | assets = []; 163 | map = {}; 164 | config.webAudioContext = null; 165 | numTotal = 0; 166 | numLoaded = 0; 167 | 168 | Object.keys(loaders).forEach(function(key) { 169 | loaders[key].destroy(); 170 | }); 171 | loaders = {}; 172 | 173 | group.emit('destroy', group.id); 174 | 175 | return group; 176 | }; 177 | 178 | // emits: progress, error, complete, destroy 179 | 180 | group = Object.create(Emitter.prototype, { 181 | _events: { 182 | value: {} 183 | }, 184 | id: { 185 | get: function() { 186 | return config.id; 187 | } 188 | }, 189 | add: { 190 | value: add 191 | }, 192 | start: { 193 | value: start 194 | }, 195 | get: { 196 | value: get 197 | }, 198 | find: { 199 | value: find 200 | }, 201 | getLoader: { 202 | value: function(id) { 203 | return loaders[id]; 204 | } 205 | }, 206 | loaded: { 207 | get: function() { 208 | return numLoaded >= numTotal; 209 | } 210 | }, 211 | file: { 212 | get: function() { 213 | return assets; 214 | } 215 | }, 216 | destroy: { 217 | value: destroy 218 | } 219 | }); 220 | 221 | config = configure(config || {}, { 222 | basePath: '', 223 | blob: false, 224 | touchLocked: false, 225 | crossOrigin: null, 226 | webAudioContext: null, 227 | log: false 228 | }); 229 | 230 | if (Array.isArray(config.assets)) { 231 | add(config.assets); 232 | } 233 | 234 | return Object.freeze(group); 235 | }; 236 | -------------------------------------------------------------------------------- /examples/js/highlight.pack.js: -------------------------------------------------------------------------------- 1 | !function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){var n=(e.className+" "+(e.parentNode?e.parentNode.className:"")).split(/\s+/);return n=n.map(function(e){return e.replace(/^lang(uage)?-/,"")}),n.filter(function(e){return N(e)||/no(-?)highlight/.test(e)})[0]}function o(e,n){var t={};for(var r in e)t[r]=e[r];if(n)for(var r in n)t[r]=n[r];return t}function i(e){var n=[];return function r(e,a){for(var o=e.firstChild;o;o=o.nextSibling)3==o.nodeType?a+=o.nodeValue.length:1==o.nodeType&&(n.push({event:"start",offset:a,node:o}),a=r(o,a),t(o).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:o}));return a}(e,0),n}function c(e,r,a){function o(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function c(e){l+=""}function u(e){("start"==e.event?i:c)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=o();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(c);do u(g.splice(0,1)[0]),g=o();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(i)}else"start"==g[0].event?f.push(g[0].node):f.pop(),u(g.splice(0,1)[0])}return l+n(a.substr(s))}function u(e){function n(e){return e&&e.source||e}function t(t,r){return RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var c={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");c[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):Object.keys(a.k).forEach(function(e){u(e,a.k[e])}),a.k=c}a.lR=t(a.l||/\b[A-Za-z0-9_]+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function s(e,t,a,o){function i(e,n){for(var t=0;t";return o+=e+'">',o+n+i}function d(){if(!w.k)return n(y);var e="",t=0;w.lR.lastIndex=0;for(var r=w.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(w,r);a?(B+=a[1],e+=p(a[0],n(r[0]))):e+=n(r[0]),t=w.lR.lastIndex,r=w.lR.exec(y)}return e+n(y.substr(t))}function h(){if(w.sL&&!R[w.sL])return n(y);var e=w.sL?s(w.sL,y,!0,L[w.sL]):l(y);return w.r>0&&(B+=e.r),"continuous"==w.subLanguageMode&&(L[w.sL]=e.top),p(e.language,e.value,!1,!0)}function v(){return void 0!==w.sL?h():d()}function b(e,t){var r=e.cN?p(e.cN,"",!0):"";e.rB?(M+=r,y=""):e.eB?(M+=n(t)+r,y=""):(M+=r,y=t),w=Object.create(e,{parent:{value:w}})}function m(e,t){if(y+=e,void 0===t)return M+=v(),0;var r=i(t,w);if(r)return M+=v(),b(r,t),r.rB?0:t.length;var a=c(w,t);if(a){var o=w;o.rE||o.eE||(y+=t),M+=v();do w.cN&&(M+=""),B+=w.r,w=w.parent;while(w!=a.parent);return o.eE&&(M+=n(t)),y="",a.starts&&b(a.starts,""),o.rE?0:t.length}if(f(t,w))throw new Error('Illegal lexeme "'+t+'" for mode "'+(w.cN||"")+'"');return y+=t,t.length||1}var x=N(e);if(!x)throw new Error('Unknown language: "'+e+'"');u(x);for(var w=o||x,L={},M="",k=w;k!=x;k=k.parent)k.cN&&(M=p(k.cN,"",!0)+M);var y="",B=0;try{for(var C,j,I=0;;){if(w.t.lastIndex=I,C=w.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}m(t.substr(I));for(var k=w;k.parent;k=k.parent)k.cN&&(M+="");return{r:B,value:M,language:e,top:w}}catch(A){if(-1!=A.message.indexOf("Illegal"))return{r:0,value:n(t)};throw A}}function l(e,t){t=t||E.languages||Object.keys(R);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(N(n)){var t=s(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function f(e){return E.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,E.tabReplace)})),E.useBR&&(e=e.replace(/\n/g,"
")),e}function g(e,n,t){var r=n?x[n]:t,a=[e.trim()];return e.match(/(\s|^)hljs(\s|$)/)||a.push("hljs"),r&&a.push(r),a.join(" ").trim()}function p(e){var n=a(e);if(!/no(-?)highlight/.test(n)){var t;E.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?s(n,r,!0):l(r),u=i(t);if(u.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(u,i(p),r)}o.value=f(o.value),e.innerHTML=o.value,e.className=g(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){E=o(E,e)}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",h,!1),addEventListener("load",h,!1)}function b(n,t){var r=R[n]=t(e);r.aliases&&r.aliases.forEach(function(e){x[e]=n})}function m(){return Object.keys(R)}function N(e){return R[e]||R[x[e]]}var E={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},R={},x={};return e.highlight=s,e.highlightAuto=l,e.fixMarkup=f,e.highlightBlock=p,e.configure=d,e.initHighlighting=h,e.initHighlightingOnLoad=v,e.registerLanguage=b,e.listLanguages=m,e.getLanguage=N,e.inherit=o,e.IR="[a-zA-Z][a-zA-Z0-9_]*",e.UIR="[a-zA-Z_][a-zA-Z0-9_]*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.CLCM={cN:"comment",b:"//",e:"$",c:[e.PWM]},e.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[e.PWM]},e.HCM={cN:"comment",b:"#",e:"$",c:[e.PWM]},e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("javascript",function(r){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",r:10,v:[{b:/^\s*('|")use strict('|")/},{b:/^\s*('|")use asm('|")/}]},r.ASM,r.QSM,r.CLCM,r.CBCM,r.CNM,{b:"("+r.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[r.CLCM,r.CBCM,r.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[r.inherit(r.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[r.CLCM,r.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+r.IR,r:0}]}}); -------------------------------------------------------------------------------- /src/loader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Emitter = require('./emitter.js'); 4 | var browserHasBlob = require('./browser-has-blob.js'); 5 | var stats = require('./stats'); 6 | 7 | module.exports = function(options) { 8 | var id = options.id; 9 | var basePath = options.basePath || ''; 10 | var url = options.url; 11 | var type = options.type; 12 | var crossOrigin = options.crossOrigin; 13 | var isTouchLocked = options.isTouchLocked; 14 | var blob = options.blob && browserHasBlob; 15 | var webAudioContext = options.webAudioContext; 16 | var log = options.log; 17 | 18 | var loader; 19 | var loadHandler; 20 | var request; 21 | var startTime; 22 | var timeout; 23 | var file; 24 | 25 | var start = function() { 26 | startTime = Date.now(); 27 | 28 | switch (type) { 29 | case 'json': 30 | loadJSON(); 31 | break; 32 | case 'jpg': 33 | case 'png': 34 | case 'gif': 35 | case 'webp': 36 | case 'svg': 37 | loadImage(); 38 | break; 39 | case 'mp3': 40 | case 'ogg': 41 | case 'opus': 42 | case 'wav': 43 | case 'm4a': 44 | loadAudio(); 45 | break; 46 | case 'ogv': 47 | case 'mp4': 48 | case 'webm': 49 | case 'hls': 50 | loadVideo(); 51 | break; 52 | case 'bin': 53 | case 'binary': 54 | loadXHR('arraybuffer'); 55 | break; 56 | case 'txt': 57 | case 'text': 58 | loadXHR('text'); 59 | break; 60 | default: 61 | throw 'AssetsLoader ERROR: Unknown type for file with URL: ' + basePath + url + ' (' + type + ')'; 62 | } 63 | }; 64 | 65 | var dispatchComplete = function(data) { 66 | if (!data) { 67 | return; 68 | } 69 | file = {id: id, file: data, type: type}; 70 | loader.emit('progress', 1); 71 | loader.emit('complete', file, id, type); 72 | removeListeners(); 73 | }; 74 | 75 | var loadXHR = function(responseType, customLoadHandler) { 76 | loadHandler = customLoadHandler || completeHandler; 77 | 78 | request = new XMLHttpRequest(); 79 | request.open('GET', basePath + url, true); 80 | request.responseType = responseType; 81 | request.addEventListener('progress', progressHandler); 82 | request.addEventListener('load', loadHandler); 83 | request.addEventListener('error', errorHandler); 84 | request.send(); 85 | }; 86 | 87 | var progressHandler = function(event) { 88 | if (event.lengthComputable) { 89 | loader.emit('progress', event.loaded / event.total); 90 | } 91 | }; 92 | 93 | var completeHandler = function() { 94 | if (success()) { 95 | dispatchComplete(request.response); 96 | } 97 | }; 98 | 99 | var success = function() { 100 | // console.log('success', url, request.status); 101 | if (request && request.status < 400) { 102 | stats.update(request, startTime, url, log); 103 | return true; 104 | } 105 | errorHandler(request && request.statusText); 106 | return false; 107 | }; 108 | 109 | // json 110 | 111 | var loadJSON = function() { 112 | loadXHR('json', function() { 113 | if (success()) { 114 | var data = request.response; 115 | if (typeof data === 'string') { 116 | data = JSON.parse(data); 117 | } 118 | dispatchComplete(data); 119 | } 120 | }); 121 | }; 122 | 123 | // image 124 | 125 | var loadImage = function() { 126 | if (blob) { 127 | loadImageBlob(); 128 | } else { 129 | loadImageElement(); 130 | } 131 | }; 132 | 133 | var loadImageElement = function() { 134 | request = new Image(); 135 | if (crossOrigin) { 136 | request.crossOrigin = 'anonymous'; 137 | } 138 | request.addEventListener('error', errorHandler, false); 139 | request.addEventListener('load', elementLoadHandler, false); 140 | request.src = basePath + url; 141 | }; 142 | 143 | var elementLoadHandler = function(event) { 144 | window.clearTimeout(timeout); 145 | if (!event && (request.error || !request.readyState)) { 146 | errorHandler(); 147 | return; 148 | } 149 | dispatchComplete(request); 150 | }; 151 | 152 | var loadImageBlob = function() { 153 | loadXHR('blob', function() { 154 | if (success()) { 155 | request = new Image(); 156 | request.addEventListener('error', errorHandler, false); 157 | request.addEventListener('load', imageBlobHandler, false); 158 | request.src = window.URL.createObjectURL(request.response); 159 | } 160 | }); 161 | }; 162 | 163 | var imageBlobHandler = function() { 164 | window.URL.revokeObjectURL(request.src); 165 | dispatchComplete(request); 166 | }; 167 | 168 | // audio 169 | 170 | var loadAudio = function() { 171 | if (webAudioContext) { 172 | loadAudioBuffer(); 173 | } else { 174 | loadMediaElement('audio'); 175 | } 176 | }; 177 | 178 | // video 179 | 180 | var loadVideo = function() { 181 | if (blob) { 182 | loadXHR('blob'); 183 | } else { 184 | loadMediaElement('video'); 185 | } 186 | }; 187 | 188 | // audio buffer 189 | 190 | var loadAudioBuffer = function() { 191 | loadXHR('arraybuffer', function() { 192 | if (success()) { 193 | webAudioContext.decodeAudioData( 194 | request.response, 195 | function(buffer) { 196 | request = null; 197 | dispatchComplete(buffer); 198 | }, 199 | function(e) { 200 | errorHandler(e); 201 | } 202 | ); 203 | } 204 | }); 205 | }; 206 | 207 | // media element 208 | 209 | var loadMediaElement = function(tagName) { 210 | request = document.createElement(tagName); 211 | 212 | if (!isTouchLocked) { 213 | // timeout because sometimes canplaythrough doesn't fire 214 | window.clearTimeout(timeout); 215 | timeout = window.setTimeout(elementLoadHandler, 2000); 216 | request.addEventListener('canplaythrough', elementLoadHandler, false); 217 | } 218 | 219 | request.addEventListener('error', errorHandler, false); 220 | request.preload = 'auto'; 221 | request.src = basePath + url; 222 | request.load(); 223 | 224 | if (isTouchLocked) { 225 | dispatchComplete(request); 226 | } 227 | }; 228 | 229 | // error 230 | 231 | var errorHandler = function(err) { 232 | // console.log('errorHandler', url, err); 233 | window.clearTimeout(timeout); 234 | 235 | var message = err; 236 | 237 | if (request && request.tagName && request.error) { 238 | var ERROR_STATE = ['', 'ABORTED', 'NETWORK', 'DECODE', 'SRC_NOT_SUPPORTED']; 239 | message = 'MediaError: ' + ERROR_STATE[request.error.code] + ' ' + request.src; 240 | } else if (request && request.statusText) { 241 | message = request.statusText; 242 | } else if (err && err.message) { 243 | message = err.message; 244 | } else if (err && err.type) { 245 | message = err.type; 246 | } 247 | 248 | loader.emit('error', 'Error loading "' + basePath + url + '" ' + message); 249 | 250 | destroy(); 251 | }; 252 | 253 | // clean up 254 | 255 | var removeListeners = function() { 256 | loader.off('error'); 257 | loader.off('progress'); 258 | loader.off('complete'); 259 | 260 | if (request) { 261 | request.removeEventListener('progress', progressHandler); 262 | request.removeEventListener('load', loadHandler); 263 | request.removeEventListener('error', errorHandler); 264 | request.removeEventListener('load', elementLoadHandler); 265 | request.removeEventListener('canplaythrough', elementLoadHandler); 266 | request.removeEventListener('load', imageBlobHandler); 267 | } 268 | }; 269 | 270 | var destroy = function() { 271 | removeListeners(); 272 | 273 | if (request && request.abort && request.readyState < 4) { 274 | request.abort(); 275 | } 276 | 277 | request = null; 278 | webAudioContext = null; 279 | file = null; 280 | 281 | window.clearTimeout(timeout); 282 | 283 | loader.emit('destroy', id); 284 | }; 285 | 286 | // emits: progress, error, complete 287 | 288 | loader = Object.create(Emitter.prototype, { 289 | _events: { 290 | value: {} 291 | }, 292 | id: { 293 | value: options.id 294 | }, 295 | start: { 296 | value: start 297 | }, 298 | loaded: { 299 | get: function() { 300 | return !!file; 301 | } 302 | }, 303 | file: { 304 | get: function() { 305 | return file; 306 | } 307 | }, 308 | destroy: { 309 | value: destroy 310 | } 311 | }); 312 | 313 | return Object.freeze(loader); 314 | }; 315 | -------------------------------------------------------------------------------- /dist/assets-loader.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.assetsLoader=e()}}(function(){return function e(t,n,r){function s(i,a){if(!n[i]){if(!t[i]){var u="function"==typeof require&&require;if(!a&&u)return u(i,!0);if(o)return o(i,!0);var c=new Error("Cannot find module '"+i+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[i]={exports:{}};t[i][0].call(l.exports,function(e){var n=t[i][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[i].exports}for(var o="function"==typeof require&&require,i=0;i0&&this._events[e].length>n&&(this._events[e].warned=!0,"function"==typeof console.trace)}return this},r.prototype.on=r.prototype.addListener,r.prototype.once=function(e,t){function n(){this.removeListener(e,n),r||(r=!0,t.apply(this,arguments))}if(!s(t))throw TypeError("listener must be a function");var r=!1;return n.listener=t,this.on(e,n),this},r.prototype.removeListener=function(e,t){var n,r,o,a;if(!s(t))throw TypeError("listener must be a function");if(!this._events||!this._events[e])return this;if(n=this._events[e],o=n.length,r=-1,n===t||s(n.listener)&&n.listener===t)delete this._events[e],this._events.removeListener&&this.emit("removeListener",e,t);else if(i(n)){for(a=o;a-- >0;)if(n[a]===t||n[a].listener&&n[a].listener===t){r=a;break}if(r<0)return this;1===n.length?(n.length=0,delete this._events[e]):n.splice(r,1),this._events.removeListener&&this.emit("removeListener",e,t)}return this},r.prototype.removeAllListeners=function(e){var t,n;if(!this._events)return this;if(!this._events.removeListener)return 0===arguments.length?this._events={}:this._events[e]&&delete this._events[e],this;if(0===arguments.length){for(t in this._events)"removeListener"!==t&&this.removeAllListeners(t);return this.removeAllListeners("removeListener"),this._events={},this}if(n=this._events[e],s(n))this.removeListener(e,n);else for(;n.length;)this.removeListener(e,n[n.length-1]);return delete this._events[e],this},r.prototype.listeners=function(e){var t;return t=this._events&&this._events[e]?s(this._events[e])?[this._events[e]]:this._events[e].slice():[]},r.listenerCount=function(e,t){var n;return n=e._events&&e._events[t]?s(e._events[t])?1:e._events[t].length:0}},{}],2:[function(e,t,n){"use strict";t.exports=function(){try{return!!new Blob}catch(e){return!1}}()},{}],3:[function(e,t,n){"use strict";function r(){s.call(this),this.setMaxListeners(20)}var s=e(1).EventEmitter;r.prototype=Object.create(s.prototype),r.prototype.constructor=r,r.prototype.off=function(e,t){return t?this.removeListener(e,t):e?this.removeAllListeners(e):this.removeAllListeners()},t.exports=r},{1:1}],4:[function(e,t,n){"use strict";var r=e(3),s=e(6),o=0;t.exports=function i(e){var t,n={},a=[],u=[],c=0,l=0,f={},d=function(n){if(Array.isArray(n))return n.forEach(d),t;var r,o=!!n.assets&&Array.isArray(n.assets);return r=o?i(m(n,e)):s(m(n,e)),r.once("destroy",_),u.push(r),f[r.id]=r,t},v=function(e){return arguments.length?n[e]?n[e]:f[e]:a},h=function(e){if(v(e))return v(e);var t=null;return Object.keys(f).some(function(n){return t=f[n].find&&f[n].find(e),!!t}),t},p=function(e){return e&&e.split("?")[0].split(".").pop().toLowerCase()},m=function(e,t){if("string"==typeof e){var n=e;e={url:n}}return void 0===e.isTouchLocked&&(e.isTouchLocked=t.isTouchLocked),void 0===e.blob&&(e.blob=t.blob),void 0===e.basePath&&(e.basePath=t.basePath),e.id=e.id||e.url||String(++o),e.type=e.type||p(e.url),e.crossOrigin=e.crossOrigin||t.crossOrigin,e.webAudioContext=e.webAudioContext||t.webAudioContext,e.log=t.log,e},g=function(){return l=u.length,u.forEach(function(e){e.on("progress",y).once("complete",b).once("error",L).start()}),u=[],t},y=function(e){var n=c+e;t.emit("progress",n/l)},b=function(e,r,s){Array.isArray(e)&&(e={id:r,file:e,type:s}),c++,t.emit("progress",c/l),n[e.id]=e.file,a.push(e),t.emit("childcomplete",e),w()},L=function(e){l--,t.listeners("error").length&&t.emit("error",e),w()},_=function(e){f[e]=null,delete f[e],n[e]=null,delete n[e],a.some(function(t,n){if(t.id===e)return a.splice(n,1),!0})},w=function(){c>=l&&t.emit("complete",a,n,e.id,"group")},x=function(){for(;u.length;)u.pop().destroy();return t.off("error"),t.off("progress"),t.off("complete"),a=[],n={},e.webAudioContext=null,l=0,c=0,Object.keys(f).forEach(function(e){f[e].destroy()}),f={},t.emit("destroy",t.id),t};return t=Object.create(r.prototype,{_events:{value:{}},id:{get:function(){return e.id}},add:{value:d},start:{value:g},get:{value:v},find:{value:h},getLoader:{value:function(e){return f[e]}},loaded:{get:function(){return c>=l}},file:{get:function(){return a}},destroy:{value:x}}),e=m(e||{},{basePath:"",blob:!1,touchLocked:!1,crossOrigin:null,webAudioContext:null,log:!1}),Array.isArray(e.assets)&&d(e.assets),Object.freeze(t)}},{3:3,6:6}],5:[function(e,t,n){"use strict";var r=e(4);r.stats=e(7),t.exports=r},{4:4,7:7}],6:[function(e,t,n){"use strict";var r=e(3),s=e(2),o=e(7);t.exports=function(e){var t,n,i,a,u,c,l=e.id,f=e.basePath||"",d=e.url,v=e.type,h=e.crossOrigin,p=e.isTouchLocked,m=e.blob&&s,g=e.webAudioContext,y=e.log,b=function(){switch(a=Date.now(),v){case"json":O();break;case"jpg":case"png":case"gif":case"webp":case"svg":A();break;case"mp3":case"ogg":case"opus":case"wav":case"m4a":R();break;case"ogv":case"mp4":case"webm":case"hls":U();break;case"bin":case"binary":_("arraybuffer");break;case"txt":case"text":_("text");break;default:throw"AssetsLoader ERROR: Unknown type for file with URL: "+f+d+" ("+v+")"}},L=function(e){e&&(c={id:l,file:e,type:v},t.emit("progress",1),t.emit("complete",c,l,v),N())},_=function(e,t){n=t||x,i=new XMLHttpRequest,i.open("GET",f+d,!0),i.responseType=e,i.addEventListener("progress",w),i.addEventListener("load",n),i.addEventListener("error",F),i.send()},w=function(e){e.lengthComputable&&t.emit("progress",e.loaded/e.total)},x=function(){E()&&L(i.response)},E=function(){return i&&i.status<400?(o.update(i,a,d,y),!0):(F(i&&i.statusText),!1)},O=function(){_("json",function(){if(E()){var e=i.response;"string"==typeof e&&(e=JSON.parse(e)),L(e)}})},A=function(){m?j():T()},T=function(){i=new Image,h&&(i.crossOrigin="anonymous"),i.addEventListener("error",F,!1),i.addEventListener("load",k,!1),i.src=f+d},k=function(e){return window.clearTimeout(u),e||!i.error&&i.readyState?void L(i):void F()},j=function(){_("blob",function(){E()&&(i=new Image,i.addEventListener("error",F,!1),i.addEventListener("load",C,!1),i.src=window.URL.createObjectURL(i.response))})},C=function(){window.URL.revokeObjectURL(i.src),L(i)},R=function(){g?D():M("audio")},U=function(){m?_("blob"):M("video")},D=function(){_("arraybuffer",function(){E()&&g.decodeAudioData(i.response,function(e){i=null,L(e)},function(e){F(e)})})},M=function(e){i=document.createElement(e),p||(window.clearTimeout(u),u=window.setTimeout(k,2e3),i.addEventListener("canplaythrough",k,!1)),i.addEventListener("error",F,!1),i.preload="auto",i.src=f+d,i.load(),p&&L(i)},F=function(e){window.clearTimeout(u);var n=e;if(i&&i.tagName&&i.error){var r=["","ABORTED","NETWORK","DECODE","SRC_NOT_SUPPORTED"];n="MediaError: "+r[i.error.code]+" "+i.src}else i&&i.statusText?n=i.statusText:e&&e.message?n=e.message:e&&e.type&&(n=e.type);t.emit("error",'Error loading "'+f+d+'" '+n),P()},N=function(){t.off("error"),t.off("progress"),t.off("complete"),i&&(i.removeEventListener("progress",w),i.removeEventListener("load",n),i.removeEventListener("error",F),i.removeEventListener("load",k),i.removeEventListener("canplaythrough",k),i.removeEventListener("load",C))},P=function(){N(),i&&i.abort&&i.readyState<4&&i.abort(),i=null,g=null,c=null,window.clearTimeout(u),t.emit("destroy",l)};return t=Object.create(r.prototype,{_events:{value:{}},id:{value:e.id},start:{value:b},loaded:{get:function(){return!!c}},file:{get:function(){return c}},destroy:{value:P}}),Object.freeze(t)}},{2:2,3:3,7:7}],7:[function(e,t,n){"use strict";t.exports={mbs:0,secs:0,update:function(e,t,n,r){var s,o=e.getAllResponseHeaders();if(o){var i=o.match(/content-length: (\d+)/i);i&&i.length&&(s=i[1])}if(s){s=parseInt(s,10);var a=s/1024/1024,u=(Date.now()-t)/1e3;this.secs+=u,this.mbs+=a,r&&this.log(n,a,u)}else r&&console.warn.call(console,"Can't get Content-Length:",n)},log:function(e,t,n){if(e){var r="File loaded: "+e.substr(e.lastIndexOf("/")+1)+" size:"+t.toFixed(2)+"mb time:"+n.toFixed(2)+"s speed:"+(t/n).toFixed(2)+"mbps";console.log.call(console,r)}var s="Total loaded: "+this.mbs.toFixed(2)+"mb time:"+this.secs.toFixed(2)+"s speed:"+this.getMbps().toFixed(2)+"mbps";console.log.call(console,s)},getMbps:function(){return this.mbs/this.secs}}},{}]},{},[5])(5)}); -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | examples 8 | 9 | 10 | 11 | 12 |
13 |
14 |

assets loader

15 |
16 | 17 |
18 |

images

19 |
20 | 21 |
22 | 23 |

 24 |   var images = [];
 25 |   for (var i = 0; i < 50; i++) {
 26 |       images.push({
 27 |           url: 'http://lorempixel.com/100/100?' + i,
 28 |           type: 'jpg'
 29 |       });
 30 |   }
 31 |   var loader = assetsLoader({
 32 |           assets: images
 33 |       })
 34 |       .on('error', function(error) {
 35 |           container.innerHTML = error;
 36 |       })
 37 |       .on('progress', function(progress) {
 38 |           container.innerHTML = (progress * 100).toFixed() + '%';
 39 |       })
 40 |       .on('complete', function(assets) {
 41 |           container.innerHTML = '';
 42 |           assets.forEach(function(asset) {
 43 |               container.appendChild(asset.file);
 44 |           })
 45 |       })
 46 |       .start();
 47 |       
48 | 49 |
50 |

video

51 |
52 | 53 |
54 | 55 |

 56 |   var loader = assetsLoader({ log: true })
 57 |       .add({
 58 |           id: 'video',
 59 |           url: 'https://ianmcgregor.co/prototypes/video/counter.webm',
 60 |           blob: true
 61 |       })
 62 |       .on('error', function(error) {
 63 |           container.innerHTML = error;
 64 |       })
 65 |       .on('progress', function(progress) {
 66 |           container.innerHTML = (progress * 100).toFixed() + '%';
 67 |       })
 68 |       .on('complete', function() {
 69 |           container.innerHTML = '';
 70 |           var src = this.get('video');
 71 |           if (window.Blob && src instanceof window.Blob) {
 72 |               src = window.URL.createObjectURL(src);
 73 |               el = document.createElement('video');
 74 |               var revoke = function() {
 75 |                   el.removeEventListener('canplaythrough', revoke);
 76 |                   window.URL.revokeObjectURL(src);
 77 |               };
 78 |               el.addEventListener('canplaythrough', revoke);
 79 |               el.preload = 'auto';
 80 |               el.src = src;
 81 |               el.load();
 82 |           } else {
 83 |               el = src
 84 |           }
 85 |           el.controls = 'controls';
 86 |           container.appendChild(el);
 87 |       })
 88 |       .start();
 89 |       
90 | 91 |
92 | 93 |
94 |

groups

95 |
96 | 97 |
98 |

group a

99 |
100 |

group b

101 |
102 |
103 | 104 |

105 |     var loader = assetsLoader()
106 |         .add({
107 |             id: 'groupA',
108 |             assets: images
109 |         })
110 |         .add({
111 |             id: 'groupB',
112 |             basePath: 'https://ianmcgregor.co/prototypes/audio/',
113 |             assets: sounds
114 |         })
115 |         .on('error', function(error) {
116 |             container.innerHTML = error;
117 |         })
118 |         .on('childcomplete', function(asset) {
119 |             console.debug('childcomplete', asset.id);
120 |         })
121 |         .on('complete', function(assets) {
122 |             assets.forEach(function(asset) {
123 |                 console.debug(asset.id);
124 |                 asset.file.forEach(function(file) {
125 |                     console.debug(' - ' + file.id);
126 |                 });
127 |             });
128 | 
129 |             containerImages.innerHTML = '';
130 |             containerAudio.innerHTML = '';
131 | 
132 |             loader.get('groupA')
133 |                 .forEach(function(asset) {
134 |                     asset.file.alt = asset.id;
135 |                     containerImages.appendChild(asset.file);
136 |                 });
137 | 
138 |             loader.get('groupB')
139 |                 .forEach(function(asset) {
140 |                     asset.file.controls = 'controls';
141 |                     containerAudio.appendChild(asset.file);
142 |                 });
143 |         });
144 | 
145 | 
146 |     loader.getLoader('groupA')
147 |         .on('progress', function(progress) {
148 |             containerImages.innerHTML = (progress * 100).toFixed() + '%';
149 |         });
150 | 
151 |     loader.getLoader('groupB')
152 |         .on('progress', function(progress) {
153 |             containerAudio.innerHTML = (progress * 100).toFixed() + '%';
154 |         });
155 | 
156 |     loader.start();
157 |     
158 | 159 | 160 | 161 | 162 | 163 | 164 | 344 | 345 | 346 | -------------------------------------------------------------------------------- /dist/assets-loader.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [ 4 | "node_modules/browser-pack/_prelude.js", 5 | "node_modules/browserify/node_modules/events/events.js", 6 | "src/browser-has-blob.js", 7 | "src/emitter.js", 8 | "src/group.js", 9 | "src/index.js", 10 | "src/loader.js", 11 | "src/stats.js" 12 | ], 13 | "names": [], 14 | "mappings": "AAAA;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC7SA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvBA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3OA;AACA;AACA;AACA;AACA;AACA;AACA;;ACNA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1TA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", 15 | "file": "generated.js", 16 | "sourceRoot": "", 17 | "sourcesContent": [ 18 | "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o 0 && this._events[type].length > m) {\n this._events[type].warned = true;\n console.error('(node) warning: possible EventEmitter memory ' +\n 'leak detected. %d listeners added. ' +\n 'Use emitter.setMaxListeners() to increase limit.',\n this._events[type].length);\n if (typeof console.trace === 'function') {\n // not supported in IE 10\n console.trace();\n }\n }\n }\n\n return this;\n};\n\nEventEmitter.prototype.on = EventEmitter.prototype.addListener;\n\nEventEmitter.prototype.once = function(type, listener) {\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n var fired = false;\n\n function g() {\n this.removeListener(type, g);\n\n if (!fired) {\n fired = true;\n listener.apply(this, arguments);\n }\n }\n\n g.listener = listener;\n this.on(type, g);\n\n return this;\n};\n\n// emits a 'removeListener' event iff the listener was removed\nEventEmitter.prototype.removeListener = function(type, listener) {\n var list, position, length, i;\n\n if (!isFunction(listener))\n throw TypeError('listener must be a function');\n\n if (!this._events || !this._events[type])\n return this;\n\n list = this._events[type];\n length = list.length;\n position = -1;\n\n if (list === listener ||\n (isFunction(list.listener) && list.listener === listener)) {\n delete this._events[type];\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n\n } else if (isObject(list)) {\n for (i = length; i-- > 0;) {\n if (list[i] === listener ||\n (list[i].listener && list[i].listener === listener)) {\n position = i;\n break;\n }\n }\n\n if (position < 0)\n return this;\n\n if (list.length === 1) {\n list.length = 0;\n delete this._events[type];\n } else {\n list.splice(position, 1);\n }\n\n if (this._events.removeListener)\n this.emit('removeListener', type, listener);\n }\n\n return this;\n};\n\nEventEmitter.prototype.removeAllListeners = function(type) {\n var key, listeners;\n\n if (!this._events)\n return this;\n\n // not listening for removeListener, no need to emit\n if (!this._events.removeListener) {\n if (arguments.length === 0)\n this._events = {};\n else if (this._events[type])\n delete this._events[type];\n return this;\n }\n\n // emit removeListener for all listeners on all events\n if (arguments.length === 0) {\n for (key in this._events) {\n if (key === 'removeListener') continue;\n this.removeAllListeners(key);\n }\n this.removeAllListeners('removeListener');\n this._events = {};\n return this;\n }\n\n listeners = this._events[type];\n\n if (isFunction(listeners)) {\n this.removeListener(type, listeners);\n } else {\n // LIFO order\n while (listeners.length)\n this.removeListener(type, listeners[listeners.length - 1]);\n }\n delete this._events[type];\n\n return this;\n};\n\nEventEmitter.prototype.listeners = function(type) {\n var ret;\n if (!this._events || !this._events[type])\n ret = [];\n else if (isFunction(this._events[type]))\n ret = [this._events[type]];\n else\n ret = this._events[type].slice();\n return ret;\n};\n\nEventEmitter.listenerCount = function(emitter, type) {\n var ret;\n if (!emitter._events || !emitter._events[type])\n ret = 0;\n else if (isFunction(emitter._events[type]))\n ret = 1;\n else\n ret = emitter._events[type].length;\n return ret;\n};\n\nfunction isFunction(arg) {\n return typeof arg === 'function';\n}\n\nfunction isNumber(arg) {\n return typeof arg === 'number';\n}\n\nfunction isObject(arg) {\n return typeof arg === 'object' && arg !== null;\n}\n\nfunction isUndefined(arg) {\n return arg === void 0;\n}\n", 20 | "'use strict';\n\nmodule.exports = (function() {\n try {\n return !!new Blob();\n } catch (e) {\n return false;\n }\n}());\n", 21 | "'use strict';\n\nvar EventEmitter = require('events').EventEmitter;\n\nfunction Emitter() {\n EventEmitter.call(this);\n this.setMaxListeners(20);\n}\n\nEmitter.prototype = Object.create(EventEmitter.prototype);\nEmitter.prototype.constructor = Emitter;\n\nEmitter.prototype.off = function(type, listener) {\n if (listener) {\n return this.removeListener(type, listener);\n }\n if (type) {\n return this.removeAllListeners(type);\n }\n return this.removeAllListeners();\n};\n\nmodule.exports = Emitter;\n", 22 | "'use strict';\n\nvar Emitter = require('./emitter.js');\nvar createLoader = require('./loader');\nvar autoId = 0;\n\nmodule.exports = function createGroup(config) {\n var group;\n var map = {};\n var assets = [];\n var queue = [];\n var numLoaded = 0;\n var numTotal = 0;\n var loaders = {};\n\n var add = function(options) {\n // console.debug('add', options);\n if (Array.isArray(options)) {\n options.forEach(add);\n return group;\n }\n var isGroup = !!options.assets && Array.isArray(options.assets);\n // console.debug('isGroup', isGroup);\n var loader;\n if (isGroup) {\n loader = createGroup(configure(options, config));\n } else {\n loader = createLoader(configure(options, config));\n }\n loader.once('destroy', destroyHandler);\n queue.push(loader);\n loaders[loader.id] = loader;\n return group;\n };\n\n var get = function(id) {\n if (!arguments.length) {\n return assets;\n }\n if (map[id]) {\n return map[id];\n }\n return loaders[id];\n };\n\n var find = function(id) {\n if (get(id)) {\n return get(id);\n }\n var found = null;\n Object.keys(loaders).some(function(key) {\n found = loaders[key].find && loaders[key].find(id);\n return !!found;\n });\n return found;\n };\n\n var getExtension = function(url) {\n return url && url.split('?')[0].split('.').pop().toLowerCase();\n };\n\n var configure = function(options, defaults) {\n if (typeof options === 'string') {\n var url = options;\n options = {\n url: url\n };\n }\n\n if (options.isTouchLocked === undefined) {\n options.isTouchLocked = defaults.isTouchLocked;\n }\n\n if (options.blob === undefined) {\n options.blob = defaults.blob;\n }\n\n if (options.basePath === undefined) {\n options.basePath = defaults.basePath;\n }\n\n options.id = options.id || options.url || String(++autoId);\n options.type = options.type || getExtension(options.url);\n options.crossOrigin = options.crossOrigin || defaults.crossOrigin;\n options.webAudioContext = options.webAudioContext || defaults.webAudioContext;\n options.log = defaults.log;\n\n return options;\n };\n\n var start = function() {\n numTotal = queue.length;\n\n queue.forEach(function(loader) {\n loader\n .on('progress', progressHandler)\n .once('complete', completeHandler)\n .once('error', errorHandler)\n .start();\n });\n\n queue = [];\n\n return group;\n };\n\n var progressHandler = function(progress) {\n var loaded = numLoaded + progress;\n group.emit('progress', loaded / numTotal);\n };\n\n var completeHandler = function(asset, id, type) {\n if (Array.isArray(asset)) {\n asset = { id: id, file: asset, type: type };\n }\n numLoaded++;\n group.emit('progress', numLoaded / numTotal);\n map[asset.id] = asset.file;\n assets.push(asset);\n group.emit('childcomplete', asset);\n checkComplete();\n };\n\n var errorHandler = function(err) {\n numTotal--;\n if (group.listeners('error').length) {\n group.emit('error', err);\n } else {\n console.error(err);\n }\n checkComplete();\n };\n\n var destroyHandler = function(id) {\n loaders[id] = null;\n delete loaders[id];\n\n map[id] = null;\n delete map[id];\n\n assets.some(function(asset, i) {\n if (asset.id === id) {\n assets.splice(i, 1);\n return true;\n }\n });\n };\n\n var checkComplete = function() {\n if (numLoaded >= numTotal) {\n group.emit('complete', assets, map, config.id, 'group');\n }\n };\n\n var destroy = function() {\n while (queue.length) {\n queue.pop().destroy();\n }\n group.off('error');\n group.off('progress');\n group.off('complete');\n assets = [];\n map = {};\n config.webAudioContext = null;\n numTotal = 0;\n numLoaded = 0;\n\n Object.keys(loaders).forEach(function(key) {\n loaders[key].destroy();\n });\n loaders = {};\n\n group.emit('destroy', group.id);\n\n return group;\n };\n\n // emits: progress, error, complete, destroy\n\n group = Object.create(Emitter.prototype, {\n _events: {\n value: {}\n },\n id: {\n get: function() {\n return config.id;\n }\n },\n add: {\n value: add\n },\n start: {\n value: start\n },\n get: {\n value: get\n },\n find: {\n value: find\n },\n getLoader: {\n value: function(id) {\n return loaders[id];\n }\n },\n loaded: {\n get: function() {\n return numLoaded >= numTotal;\n }\n },\n file: {\n get: function() {\n return assets;\n }\n },\n destroy: {\n value: destroy\n }\n });\n\n config = configure(config || {}, {\n basePath: '',\n blob: false,\n touchLocked: false,\n crossOrigin: null,\n webAudioContext: null,\n log: false\n });\n\n if (Array.isArray(config.assets)) {\n add(config.assets);\n }\n\n return Object.freeze(group);\n};\n", 23 | "'use strict';\n\nvar assetsLoader = require('./group');\nassetsLoader.stats = require('./stats');\n\nmodule.exports = assetsLoader;\n", 24 | "'use strict';\n\nvar Emitter = require('./emitter.js');\nvar browserHasBlob = require('./browser-has-blob.js');\nvar stats = require('./stats');\n\nmodule.exports = function(options) {\n var id = options.id;\n var basePath = options.basePath || '';\n var url = options.url;\n var type = options.type;\n var crossOrigin = options.crossOrigin;\n var isTouchLocked = options.isTouchLocked;\n var blob = options.blob && browserHasBlob;\n var webAudioContext = options.webAudioContext;\n var log = options.log;\n\n var loader;\n var loadHandler;\n var request;\n var startTime;\n var timeout;\n var file;\n\n var start = function() {\n startTime = Date.now();\n\n switch (type) {\n case 'json':\n loadJSON();\n break;\n case 'jpg':\n case 'png':\n case 'gif':\n case 'webp':\n case 'svg':\n loadImage();\n break;\n case 'mp3':\n case 'ogg':\n case 'opus':\n case 'wav':\n case 'm4a':\n loadAudio();\n break;\n case 'ogv':\n case 'mp4':\n case 'webm':\n case 'hls':\n loadVideo();\n break;\n case 'bin':\n case 'binary':\n loadXHR('arraybuffer');\n break;\n case 'txt':\n case 'text':\n loadXHR('text');\n break;\n default:\n throw 'AssetsLoader ERROR: Unknown type for file with URL: ' + basePath + url + ' (' + type + ')';\n }\n };\n\n var dispatchComplete = function(data) {\n if (!data) {\n return;\n }\n file = {id: id, file: data, type: type};\n loader.emit('progress', 1);\n loader.emit('complete', file, id, type);\n removeListeners();\n };\n\n var loadXHR = function(responseType, customLoadHandler) {\n loadHandler = customLoadHandler || completeHandler;\n\n request = new XMLHttpRequest();\n request.open('GET', basePath + url, true);\n request.responseType = responseType;\n request.addEventListener('progress', progressHandler);\n request.addEventListener('load', loadHandler);\n request.addEventListener('error', errorHandler);\n request.send();\n };\n\n var progressHandler = function(event) {\n if (event.lengthComputable) {\n loader.emit('progress', event.loaded / event.total);\n }\n };\n\n var completeHandler = function() {\n if (success()) {\n dispatchComplete(request.response);\n }\n };\n\n var success = function() {\n // console.log('success', url, request.status);\n if (request && request.status < 400) {\n stats.update(request, startTime, url, log);\n return true;\n }\n errorHandler(request && request.statusText);\n return false;\n };\n\n // json\n\n var loadJSON = function() {\n loadXHR('json', function() {\n if (success()) {\n var data = request.response;\n if (typeof data === 'string') {\n data = JSON.parse(data);\n }\n dispatchComplete(data);\n }\n });\n };\n\n // image\n\n var loadImage = function() {\n if (blob) {\n loadImageBlob();\n } else {\n loadImageElement();\n }\n };\n\n var loadImageElement = function() {\n request = new Image();\n if (crossOrigin) {\n request.crossOrigin = 'anonymous';\n }\n request.addEventListener('error', errorHandler, false);\n request.addEventListener('load', elementLoadHandler, false);\n request.src = basePath + url;\n };\n\n var elementLoadHandler = function(event) {\n window.clearTimeout(timeout);\n if (!event && (request.error || !request.readyState)) {\n errorHandler();\n return;\n }\n dispatchComplete(request);\n };\n\n var loadImageBlob = function() {\n loadXHR('blob', function() {\n if (success()) {\n request = new Image();\n request.addEventListener('error', errorHandler, false);\n request.addEventListener('load', imageBlobHandler, false);\n request.src = window.URL.createObjectURL(request.response);\n }\n });\n };\n\n var imageBlobHandler = function() {\n window.URL.revokeObjectURL(request.src);\n dispatchComplete(request);\n };\n\n // audio\n\n var loadAudio = function() {\n if (webAudioContext) {\n loadAudioBuffer();\n } else {\n loadMediaElement('audio');\n }\n };\n\n // video\n\n var loadVideo = function() {\n if (blob) {\n loadXHR('blob');\n } else {\n loadMediaElement('video');\n }\n };\n\n // audio buffer\n\n var loadAudioBuffer = function() {\n loadXHR('arraybuffer', function() {\n if (success()) {\n webAudioContext.decodeAudioData(\n request.response,\n function(buffer) {\n request = null;\n dispatchComplete(buffer);\n },\n function(e) {\n errorHandler(e);\n }\n );\n }\n });\n };\n\n // media element\n\n var loadMediaElement = function(tagName) {\n request = document.createElement(tagName);\n\n if (!isTouchLocked) {\n // timeout because sometimes canplaythrough doesn't fire\n window.clearTimeout(timeout);\n timeout = window.setTimeout(elementLoadHandler, 2000);\n request.addEventListener('canplaythrough', elementLoadHandler, false);\n }\n\n request.addEventListener('error', errorHandler, false);\n request.preload = 'auto';\n request.src = basePath + url;\n request.load();\n\n if (isTouchLocked) {\n dispatchComplete(request);\n }\n };\n\n // error\n\n var errorHandler = function(err) {\n // console.log('errorHandler', url, err);\n window.clearTimeout(timeout);\n\n var message = err;\n\n if (request && request.tagName && request.error) {\n var ERROR_STATE = ['', 'ABORTED', 'NETWORK', 'DECODE', 'SRC_NOT_SUPPORTED'];\n message = 'MediaError: ' + ERROR_STATE[request.error.code] + ' ' + request.src;\n } else if (request && request.statusText) {\n message = request.statusText;\n } else if (err && err.message) {\n message = err.message;\n } else if (err && err.type) {\n message = err.type;\n }\n\n loader.emit('error', 'Error loading \"' + basePath + url + '\" ' + message);\n\n destroy();\n };\n\n // clean up\n\n var removeListeners = function() {\n loader.off('error');\n loader.off('progress');\n loader.off('complete');\n\n if (request) {\n request.removeEventListener('progress', progressHandler);\n request.removeEventListener('load', loadHandler);\n request.removeEventListener('error', errorHandler);\n request.removeEventListener('load', elementLoadHandler);\n request.removeEventListener('canplaythrough', elementLoadHandler);\n request.removeEventListener('load', imageBlobHandler);\n }\n };\n\n var destroy = function() {\n removeListeners();\n\n if (request && request.abort && request.readyState < 4) {\n request.abort();\n }\n\n request = null;\n webAudioContext = null;\n file = null;\n\n window.clearTimeout(timeout);\n\n loader.emit('destroy', id);\n };\n\n // emits: progress, error, complete\n\n loader = Object.create(Emitter.prototype, {\n _events: {\n value: {}\n },\n id: {\n value: options.id\n },\n start: {\n value: start\n },\n loaded: {\n get: function() {\n return !!file;\n }\n },\n file: {\n get: function() {\n return file;\n }\n },\n destroy: {\n value: destroy\n }\n });\n\n return Object.freeze(loader);\n};\n", 25 | "'use strict';\n\nmodule.exports = {\n mbs: 0,\n secs: 0,\n update: function(request, startTime, url, log) {\n var length;\n var headers = request.getAllResponseHeaders();\n if (headers) {\n var match = headers.match(/content-length: (\\d+)/i);\n if (match && match.length) {\n length = match[1];\n }\n }\n // var length = request.getResponseHeader('Content-Length');\n if (length) {\n length = parseInt(length, 10);\n var mbs = length / 1024 / 1024;\n var secs = (Date.now() - startTime) / 1000;\n this.secs += secs;\n this.mbs += mbs;\n if (log) {\n this.log(url, mbs, secs);\n }\n } else if(log) {\n console.warn.call(console, 'Can\\'t get Content-Length:', url);\n }\n },\n log: function(url, mbs, secs) {\n if (url) {\n var file = 'File loaded: ' +\n url.substr(url.lastIndexOf('/') + 1) +\n ' size:' + mbs.toFixed(2) + 'mb' +\n ' time:' + secs.toFixed(2) + 's' +\n ' speed:' + (mbs / secs).toFixed(2) + 'mbps';\n\n console.log.call(console, file);\n }\n var total = 'Total loaded: ' + this.mbs.toFixed(2) + 'mb' +\n ' time:' + this.secs.toFixed(2) + 's' +\n ' speed:' + this.getMbps().toFixed(2) + 'mbps';\n console.log.call(console, total);\n },\n getMbps: function() {\n return this.mbs / this.secs;\n }\n};\n" 26 | ] 27 | } -------------------------------------------------------------------------------- /dist/assets-loader.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.assetsLoader = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && this._events[type].length > m) { 142 | this._events[type].warned = true; 143 | void 0; 144 | if (typeof console.trace === 'function') { 145 | // not supported in IE 10 146 | void 0; 147 | } 148 | } 149 | } 150 | 151 | return this; 152 | }; 153 | 154 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 155 | 156 | EventEmitter.prototype.once = function(type, listener) { 157 | if (!isFunction(listener)) 158 | throw TypeError('listener must be a function'); 159 | 160 | var fired = false; 161 | 162 | function g() { 163 | this.removeListener(type, g); 164 | 165 | if (!fired) { 166 | fired = true; 167 | listener.apply(this, arguments); 168 | } 169 | } 170 | 171 | g.listener = listener; 172 | this.on(type, g); 173 | 174 | return this; 175 | }; 176 | 177 | // emits a 'removeListener' event iff the listener was removed 178 | EventEmitter.prototype.removeListener = function(type, listener) { 179 | var list, position, length, i; 180 | 181 | if (!isFunction(listener)) 182 | throw TypeError('listener must be a function'); 183 | 184 | if (!this._events || !this._events[type]) 185 | return this; 186 | 187 | list = this._events[type]; 188 | length = list.length; 189 | position = -1; 190 | 191 | if (list === listener || 192 | (isFunction(list.listener) && list.listener === listener)) { 193 | delete this._events[type]; 194 | if (this._events.removeListener) 195 | this.emit('removeListener', type, listener); 196 | 197 | } else if (isObject(list)) { 198 | for (i = length; i-- > 0;) { 199 | if (list[i] === listener || 200 | (list[i].listener && list[i].listener === listener)) { 201 | position = i; 202 | break; 203 | } 204 | } 205 | 206 | if (position < 0) 207 | return this; 208 | 209 | if (list.length === 1) { 210 | list.length = 0; 211 | delete this._events[type]; 212 | } else { 213 | list.splice(position, 1); 214 | } 215 | 216 | if (this._events.removeListener) 217 | this.emit('removeListener', type, listener); 218 | } 219 | 220 | return this; 221 | }; 222 | 223 | EventEmitter.prototype.removeAllListeners = function(type) { 224 | var key, listeners; 225 | 226 | if (!this._events) 227 | return this; 228 | 229 | // not listening for removeListener, no need to emit 230 | if (!this._events.removeListener) { 231 | if (arguments.length === 0) 232 | this._events = {}; 233 | else if (this._events[type]) 234 | delete this._events[type]; 235 | return this; 236 | } 237 | 238 | // emit removeListener for all listeners on all events 239 | if (arguments.length === 0) { 240 | for (key in this._events) { 241 | if (key === 'removeListener') continue; 242 | this.removeAllListeners(key); 243 | } 244 | this.removeAllListeners('removeListener'); 245 | this._events = {}; 246 | return this; 247 | } 248 | 249 | listeners = this._events[type]; 250 | 251 | if (isFunction(listeners)) { 252 | this.removeListener(type, listeners); 253 | } else { 254 | // LIFO order 255 | while (listeners.length) 256 | this.removeListener(type, listeners[listeners.length - 1]); 257 | } 258 | delete this._events[type]; 259 | 260 | return this; 261 | }; 262 | 263 | EventEmitter.prototype.listeners = function(type) { 264 | var ret; 265 | if (!this._events || !this._events[type]) 266 | ret = []; 267 | else if (isFunction(this._events[type])) 268 | ret = [this._events[type]]; 269 | else 270 | ret = this._events[type].slice(); 271 | return ret; 272 | }; 273 | 274 | EventEmitter.listenerCount = function(emitter, type) { 275 | var ret; 276 | if (!emitter._events || !emitter._events[type]) 277 | ret = 0; 278 | else if (isFunction(emitter._events[type])) 279 | ret = 1; 280 | else 281 | ret = emitter._events[type].length; 282 | return ret; 283 | }; 284 | 285 | function isFunction(arg) { 286 | return typeof arg === 'function'; 287 | } 288 | 289 | function isNumber(arg) { 290 | return typeof arg === 'number'; 291 | } 292 | 293 | function isObject(arg) { 294 | return typeof arg === 'object' && arg !== null; 295 | } 296 | 297 | function isUndefined(arg) { 298 | return arg === void 0; 299 | } 300 | 301 | },{}],2:[function(_dereq_,module,exports){ 302 | 'use strict'; 303 | 304 | module.exports = (function() { 305 | try { 306 | return !!new Blob(); 307 | } catch (e) { 308 | return false; 309 | } 310 | }()); 311 | 312 | },{}],3:[function(_dereq_,module,exports){ 313 | 'use strict'; 314 | 315 | var EventEmitter = _dereq_('events').EventEmitter; 316 | 317 | function Emitter() { 318 | EventEmitter.call(this); 319 | this.setMaxListeners(20); 320 | } 321 | 322 | Emitter.prototype = Object.create(EventEmitter.prototype); 323 | Emitter.prototype.constructor = Emitter; 324 | 325 | Emitter.prototype.off = function(type, listener) { 326 | if (listener) { 327 | return this.removeListener(type, listener); 328 | } 329 | if (type) { 330 | return this.removeAllListeners(type); 331 | } 332 | return this.removeAllListeners(); 333 | }; 334 | 335 | module.exports = Emitter; 336 | 337 | },{"events":1}],4:[function(_dereq_,module,exports){ 338 | 'use strict'; 339 | 340 | var Emitter = _dereq_('./emitter.js'); 341 | var createLoader = _dereq_('./loader'); 342 | var autoId = 0; 343 | 344 | module.exports = function createGroup(config) { 345 | var group; 346 | var map = {}; 347 | var assets = []; 348 | var queue = []; 349 | var numLoaded = 0; 350 | var numTotal = 0; 351 | var loaders = {}; 352 | 353 | var add = function(options) { 354 | // console.debug('add', options); 355 | if (Array.isArray(options)) { 356 | options.forEach(add); 357 | return group; 358 | } 359 | var isGroup = !!options.assets && Array.isArray(options.assets); 360 | // console.debug('isGroup', isGroup); 361 | var loader; 362 | if (isGroup) { 363 | loader = createGroup(configure(options, config)); 364 | } else { 365 | loader = createLoader(configure(options, config)); 366 | } 367 | loader.once('destroy', destroyHandler); 368 | queue.push(loader); 369 | loaders[loader.id] = loader; 370 | return group; 371 | }; 372 | 373 | var get = function(id) { 374 | if (!arguments.length) { 375 | return assets; 376 | } 377 | if (map[id]) { 378 | return map[id]; 379 | } 380 | return loaders[id]; 381 | }; 382 | 383 | var find = function(id) { 384 | if (get(id)) { 385 | return get(id); 386 | } 387 | var found = null; 388 | Object.keys(loaders).some(function(key) { 389 | found = loaders[key].find && loaders[key].find(id); 390 | return !!found; 391 | }); 392 | return found; 393 | }; 394 | 395 | var getExtension = function(url) { 396 | return url && url.split('?')[0].split('.').pop().toLowerCase(); 397 | }; 398 | 399 | var configure = function(options, defaults) { 400 | if (typeof options === 'string') { 401 | var url = options; 402 | options = { 403 | url: url 404 | }; 405 | } 406 | 407 | if (options.isTouchLocked === undefined) { 408 | options.isTouchLocked = defaults.isTouchLocked; 409 | } 410 | 411 | if (options.blob === undefined) { 412 | options.blob = defaults.blob; 413 | } 414 | 415 | if (options.basePath === undefined) { 416 | options.basePath = defaults.basePath; 417 | } 418 | 419 | options.id = options.id || options.url || String(++autoId); 420 | options.type = options.type || getExtension(options.url); 421 | options.crossOrigin = options.crossOrigin || defaults.crossOrigin; 422 | options.webAudioContext = options.webAudioContext || defaults.webAudioContext; 423 | options.log = defaults.log; 424 | 425 | return options; 426 | }; 427 | 428 | var start = function() { 429 | numTotal = queue.length; 430 | 431 | queue.forEach(function(loader) { 432 | loader 433 | .on('progress', progressHandler) 434 | .once('complete', completeHandler) 435 | .once('error', errorHandler) 436 | .start(); 437 | }); 438 | 439 | queue = []; 440 | 441 | return group; 442 | }; 443 | 444 | var progressHandler = function(progress) { 445 | var loaded = numLoaded + progress; 446 | group.emit('progress', loaded / numTotal); 447 | }; 448 | 449 | var completeHandler = function(asset, id, type) { 450 | if (Array.isArray(asset)) { 451 | asset = { id: id, file: asset, type: type }; 452 | } 453 | numLoaded++; 454 | group.emit('progress', numLoaded / numTotal); 455 | map[asset.id] = asset.file; 456 | assets.push(asset); 457 | group.emit('childcomplete', asset); 458 | checkComplete(); 459 | }; 460 | 461 | var errorHandler = function(err) { 462 | numTotal--; 463 | if (group.listeners('error').length) { 464 | group.emit('error', err); 465 | } else { 466 | void 0; 467 | } 468 | checkComplete(); 469 | }; 470 | 471 | var destroyHandler = function(id) { 472 | loaders[id] = null; 473 | delete loaders[id]; 474 | 475 | map[id] = null; 476 | delete map[id]; 477 | 478 | assets.some(function(asset, i) { 479 | if (asset.id === id) { 480 | assets.splice(i, 1); 481 | return true; 482 | } 483 | }); 484 | }; 485 | 486 | var checkComplete = function() { 487 | if (numLoaded >= numTotal) { 488 | group.emit('complete', assets, map, config.id, 'group'); 489 | } 490 | }; 491 | 492 | var destroy = function() { 493 | while (queue.length) { 494 | queue.pop().destroy(); 495 | } 496 | group.off('error'); 497 | group.off('progress'); 498 | group.off('complete'); 499 | assets = []; 500 | map = {}; 501 | config.webAudioContext = null; 502 | numTotal = 0; 503 | numLoaded = 0; 504 | 505 | Object.keys(loaders).forEach(function(key) { 506 | loaders[key].destroy(); 507 | }); 508 | loaders = {}; 509 | 510 | group.emit('destroy', group.id); 511 | 512 | return group; 513 | }; 514 | 515 | // emits: progress, error, complete, destroy 516 | 517 | group = Object.create(Emitter.prototype, { 518 | _events: { 519 | value: {} 520 | }, 521 | id: { 522 | get: function() { 523 | return config.id; 524 | } 525 | }, 526 | add: { 527 | value: add 528 | }, 529 | start: { 530 | value: start 531 | }, 532 | get: { 533 | value: get 534 | }, 535 | find: { 536 | value: find 537 | }, 538 | getLoader: { 539 | value: function(id) { 540 | return loaders[id]; 541 | } 542 | }, 543 | loaded: { 544 | get: function() { 545 | return numLoaded >= numTotal; 546 | } 547 | }, 548 | file: { 549 | get: function() { 550 | return assets; 551 | } 552 | }, 553 | destroy: { 554 | value: destroy 555 | } 556 | }); 557 | 558 | config = configure(config || {}, { 559 | basePath: '', 560 | blob: false, 561 | touchLocked: false, 562 | crossOrigin: null, 563 | webAudioContext: null, 564 | log: false 565 | }); 566 | 567 | if (Array.isArray(config.assets)) { 568 | add(config.assets); 569 | } 570 | 571 | return Object.freeze(group); 572 | }; 573 | 574 | },{"./emitter.js":3,"./loader":6}],5:[function(_dereq_,module,exports){ 575 | 'use strict'; 576 | 577 | var assetsLoader = _dereq_('./group'); 578 | assetsLoader.stats = _dereq_('./stats'); 579 | 580 | module.exports = assetsLoader; 581 | 582 | },{"./group":4,"./stats":7}],6:[function(_dereq_,module,exports){ 583 | 'use strict'; 584 | 585 | var Emitter = _dereq_('./emitter.js'); 586 | var browserHasBlob = _dereq_('./browser-has-blob.js'); 587 | var stats = _dereq_('./stats'); 588 | 589 | module.exports = function(options) { 590 | var id = options.id; 591 | var basePath = options.basePath || ''; 592 | var url = options.url; 593 | var type = options.type; 594 | var crossOrigin = options.crossOrigin; 595 | var isTouchLocked = options.isTouchLocked; 596 | var blob = options.blob && browserHasBlob; 597 | var webAudioContext = options.webAudioContext; 598 | var log = options.log; 599 | 600 | var loader; 601 | var loadHandler; 602 | var request; 603 | var startTime; 604 | var timeout; 605 | var file; 606 | 607 | var start = function() { 608 | startTime = Date.now(); 609 | 610 | switch (type) { 611 | case 'json': 612 | loadJSON(); 613 | break; 614 | case 'jpg': 615 | case 'png': 616 | case 'gif': 617 | case 'webp': 618 | case 'svg': 619 | loadImage(); 620 | break; 621 | case 'mp3': 622 | case 'ogg': 623 | case 'opus': 624 | case 'wav': 625 | case 'm4a': 626 | loadAudio(); 627 | break; 628 | case 'ogv': 629 | case 'mp4': 630 | case 'webm': 631 | case 'hls': 632 | loadVideo(); 633 | break; 634 | case 'bin': 635 | case 'binary': 636 | loadXHR('arraybuffer'); 637 | break; 638 | case 'txt': 639 | case 'text': 640 | loadXHR('text'); 641 | break; 642 | default: 643 | throw 'AssetsLoader ERROR: Unknown type for file with URL: ' + basePath + url + ' (' + type + ')'; 644 | } 645 | }; 646 | 647 | var dispatchComplete = function(data) { 648 | if (!data) { 649 | return; 650 | } 651 | file = {id: id, file: data, type: type}; 652 | loader.emit('progress', 1); 653 | loader.emit('complete', file, id, type); 654 | removeListeners(); 655 | }; 656 | 657 | var loadXHR = function(responseType, customLoadHandler) { 658 | loadHandler = customLoadHandler || completeHandler; 659 | 660 | request = new XMLHttpRequest(); 661 | request.open('GET', basePath + url, true); 662 | request.responseType = responseType; 663 | request.addEventListener('progress', progressHandler); 664 | request.addEventListener('load', loadHandler); 665 | request.addEventListener('error', errorHandler); 666 | request.send(); 667 | }; 668 | 669 | var progressHandler = function(event) { 670 | if (event.lengthComputable) { 671 | loader.emit('progress', event.loaded / event.total); 672 | } 673 | }; 674 | 675 | var completeHandler = function() { 676 | if (success()) { 677 | dispatchComplete(request.response); 678 | } 679 | }; 680 | 681 | var success = function() { 682 | // console.log('success', url, request.status); 683 | if (request && request.status < 400) { 684 | stats.update(request, startTime, url, log); 685 | return true; 686 | } 687 | errorHandler(request && request.statusText); 688 | return false; 689 | }; 690 | 691 | // json 692 | 693 | var loadJSON = function() { 694 | loadXHR('json', function() { 695 | if (success()) { 696 | var data = request.response; 697 | if (typeof data === 'string') { 698 | data = JSON.parse(data); 699 | } 700 | dispatchComplete(data); 701 | } 702 | }); 703 | }; 704 | 705 | // image 706 | 707 | var loadImage = function() { 708 | if (blob) { 709 | loadImageBlob(); 710 | } else { 711 | loadImageElement(); 712 | } 713 | }; 714 | 715 | var loadImageElement = function() { 716 | request = new Image(); 717 | if (crossOrigin) { 718 | request.crossOrigin = 'anonymous'; 719 | } 720 | request.addEventListener('error', errorHandler, false); 721 | request.addEventListener('load', elementLoadHandler, false); 722 | request.src = basePath + url; 723 | }; 724 | 725 | var elementLoadHandler = function(event) { 726 | window.clearTimeout(timeout); 727 | if (!event && (request.error || !request.readyState)) { 728 | errorHandler(); 729 | return; 730 | } 731 | dispatchComplete(request); 732 | }; 733 | 734 | var loadImageBlob = function() { 735 | loadXHR('blob', function() { 736 | if (success()) { 737 | request = new Image(); 738 | request.addEventListener('error', errorHandler, false); 739 | request.addEventListener('load', imageBlobHandler, false); 740 | request.src = window.URL.createObjectURL(request.response); 741 | } 742 | }); 743 | }; 744 | 745 | var imageBlobHandler = function() { 746 | window.URL.revokeObjectURL(request.src); 747 | dispatchComplete(request); 748 | }; 749 | 750 | // audio 751 | 752 | var loadAudio = function() { 753 | if (webAudioContext) { 754 | loadAudioBuffer(); 755 | } else { 756 | loadMediaElement('audio'); 757 | } 758 | }; 759 | 760 | // video 761 | 762 | var loadVideo = function() { 763 | if (blob) { 764 | loadXHR('blob'); 765 | } else { 766 | loadMediaElement('video'); 767 | } 768 | }; 769 | 770 | // audio buffer 771 | 772 | var loadAudioBuffer = function() { 773 | loadXHR('arraybuffer', function() { 774 | if (success()) { 775 | webAudioContext.decodeAudioData( 776 | request.response, 777 | function(buffer) { 778 | request = null; 779 | dispatchComplete(buffer); 780 | }, 781 | function(e) { 782 | errorHandler(e); 783 | } 784 | ); 785 | } 786 | }); 787 | }; 788 | 789 | // media element 790 | 791 | var loadMediaElement = function(tagName) { 792 | request = document.createElement(tagName); 793 | 794 | if (!isTouchLocked) { 795 | // timeout because sometimes canplaythrough doesn't fire 796 | window.clearTimeout(timeout); 797 | timeout = window.setTimeout(elementLoadHandler, 2000); 798 | request.addEventListener('canplaythrough', elementLoadHandler, false); 799 | } 800 | 801 | request.addEventListener('error', errorHandler, false); 802 | request.preload = 'auto'; 803 | request.src = basePath + url; 804 | request.load(); 805 | 806 | if (isTouchLocked) { 807 | dispatchComplete(request); 808 | } 809 | }; 810 | 811 | // error 812 | 813 | var errorHandler = function(err) { 814 | // console.log('errorHandler', url, err); 815 | window.clearTimeout(timeout); 816 | 817 | var message = err; 818 | 819 | if (request && request.tagName && request.error) { 820 | var ERROR_STATE = ['', 'ABORTED', 'NETWORK', 'DECODE', 'SRC_NOT_SUPPORTED']; 821 | message = 'MediaError: ' + ERROR_STATE[request.error.code] + ' ' + request.src; 822 | } else if (request && request.statusText) { 823 | message = request.statusText; 824 | } else if (err && err.message) { 825 | message = err.message; 826 | } else if (err && err.type) { 827 | message = err.type; 828 | } 829 | 830 | loader.emit('error', 'Error loading "' + basePath + url + '" ' + message); 831 | 832 | destroy(); 833 | }; 834 | 835 | // clean up 836 | 837 | var removeListeners = function() { 838 | loader.off('error'); 839 | loader.off('progress'); 840 | loader.off('complete'); 841 | 842 | if (request) { 843 | request.removeEventListener('progress', progressHandler); 844 | request.removeEventListener('load', loadHandler); 845 | request.removeEventListener('error', errorHandler); 846 | request.removeEventListener('load', elementLoadHandler); 847 | request.removeEventListener('canplaythrough', elementLoadHandler); 848 | request.removeEventListener('load', imageBlobHandler); 849 | } 850 | }; 851 | 852 | var destroy = function() { 853 | removeListeners(); 854 | 855 | if (request && request.abort && request.readyState < 4) { 856 | request.abort(); 857 | } 858 | 859 | request = null; 860 | webAudioContext = null; 861 | file = null; 862 | 863 | window.clearTimeout(timeout); 864 | 865 | loader.emit('destroy', id); 866 | }; 867 | 868 | // emits: progress, error, complete 869 | 870 | loader = Object.create(Emitter.prototype, { 871 | _events: { 872 | value: {} 873 | }, 874 | id: { 875 | value: options.id 876 | }, 877 | start: { 878 | value: start 879 | }, 880 | loaded: { 881 | get: function() { 882 | return !!file; 883 | } 884 | }, 885 | file: { 886 | get: function() { 887 | return file; 888 | } 889 | }, 890 | destroy: { 891 | value: destroy 892 | } 893 | }); 894 | 895 | return Object.freeze(loader); 896 | }; 897 | 898 | },{"./browser-has-blob.js":2,"./emitter.js":3,"./stats":7}],7:[function(_dereq_,module,exports){ 899 | 'use strict'; 900 | 901 | module.exports = { 902 | mbs: 0, 903 | secs: 0, 904 | update: function(request, startTime, url, log) { 905 | var length; 906 | var headers = request.getAllResponseHeaders(); 907 | if (headers) { 908 | var match = headers.match(/content-length: (\d+)/i); 909 | if (match && match.length) { 910 | length = match[1]; 911 | } 912 | } 913 | // var length = request.getResponseHeader('Content-Length'); 914 | if (length) { 915 | length = parseInt(length, 10); 916 | var mbs = length / 1024 / 1024; 917 | var secs = (Date.now() - startTime) / 1000; 918 | this.secs += secs; 919 | this.mbs += mbs; 920 | if (log) { 921 | this.log(url, mbs, secs); 922 | } 923 | } else if(log) { 924 | console.warn.call(console, 'Can\'t get Content-Length:', url); 925 | } 926 | }, 927 | log: function(url, mbs, secs) { 928 | if (url) { 929 | var file = 'File loaded: ' + 930 | url.substr(url.lastIndexOf('/') + 1) + 931 | ' size:' + mbs.toFixed(2) + 'mb' + 932 | ' time:' + secs.toFixed(2) + 's' + 933 | ' speed:' + (mbs / secs).toFixed(2) + 'mbps'; 934 | 935 | console.log.call(console, file); 936 | } 937 | var total = 'Total loaded: ' + this.mbs.toFixed(2) + 'mb' + 938 | ' time:' + this.secs.toFixed(2) + 's' + 939 | ' speed:' + this.getMbps().toFixed(2) + 'mbps'; 940 | console.log.call(console, total); 941 | }, 942 | getMbps: function() { 943 | return this.mbs / this.secs; 944 | } 945 | }; 946 | 947 | },{}]},{},[5])(5) 948 | }); 949 | //# sourceMappingURL=assets-loader.js.map 950 | --------------------------------------------------------------------------------