Skip to content
208 changes: 99 additions & 109 deletions src/core/a-assets.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* global customElements */
var ANode = require('./a-node');
var bind = require('../utils/bind');
var debug = require('../utils/debug');
var registerElement = require('./a-register-element').registerElement;
var THREE = require('../lib/three');

var fileLoader = new THREE.FileLoader();
Expand All @@ -10,126 +10,116 @@ var warn = debug('core:a-assets:warn');
/**
* Asset management system. Handles blocking on asset loading.
*/
module.exports = registerElement('a-assets', {
prototype: Object.create(ANode.prototype, {
createdCallback: {
value: function () {
this.isAssets = true;
this.fileLoader = fileLoader;
this.timeout = null;
}
},

attachedCallback: {
value: function () {
var self = this;
var i;
var loaded = [];
var mediaEl;
var mediaEls;
var imgEl;
var imgEls;
var timeout;
class AAssets extends ANode {
constructor () {
super();
this.isAssets = true;
this.fileLoader = fileLoader;
this.timeout = null;
}

if (!this.parentNode.isScene) {
throw new Error('<a-assets> must be a child of a <a-scene>.');
}
connectedCallback () {
var self = this;
var i;
var loaded = [];
var mediaEl;
var mediaEls;
var imgEl;
var imgEls;
var timeout;

if (!this.parentNode.isScene) {
throw new Error('<a-assets> must be a child of a <a-scene>.');
}

// Wait for <img>s.
imgEls = this.querySelectorAll('img');
for (i = 0; i < imgEls.length; i++) {
imgEl = fixUpMediaElement(imgEls[i]);
loaded.push(new Promise(function (resolve, reject) {
// Set in cache because we won't be needing to call three.js loader if we have.
// a loaded media element.
THREE.Cache.files[imgEls[i].getAttribute('src')] = imgEl;
imgEl.onload = resolve;
imgEl.onerror = reject;
}));
}
// Wait for <img>s.
imgEls = this.querySelectorAll('img');
for (i = 0; i < imgEls.length; i++) {
imgEl = fixUpMediaElement(imgEls[i]);
loaded.push(new Promise(function (resolve, reject) {
// Set in cache because we won't be needing to call three.js loader if we have.
// a loaded media element.
THREE.Cache.files[imgEls[i].getAttribute('src')] = imgEl;
imgEl.onload = resolve;
imgEl.onerror = reject;
}));
}

// Wait for <audio>s and <video>s.
mediaEls = this.querySelectorAll('audio, video');
for (i = 0; i < mediaEls.length; i++) {
mediaEl = fixUpMediaElement(mediaEls[i]);
if (!mediaEl.src && !mediaEl.srcObject) {
warn('Audio/video asset has neither `src` nor `srcObject` attributes.');
}
loaded.push(mediaElementLoaded(mediaEl));
}
// Wait for <audio>s and <video>s.
mediaEls = this.querySelectorAll('audio, video');
for (i = 0; i < mediaEls.length; i++) {
mediaEl = fixUpMediaElement(mediaEls[i]);
if (!mediaEl.src && !mediaEl.srcObject) {
warn('Audio/video asset has neither `src` nor `srcObject` attributes.');
}
loaded.push(mediaElementLoaded(mediaEl));
}

// Trigger loaded for scene to start rendering.
Promise.all(loaded).then(bind(this.load, this));
// Trigger loaded for scene to start rendering.
Promise.all(loaded).then(bind(this.load, this));

// Timeout to start loading anyways.
timeout = parseInt(this.getAttribute('timeout'), 10) || 3000;
this.timeout = setTimeout(function () {
if (self.hasLoaded) { return; }
warn('Asset loading timed out in ', timeout, 'ms');
self.emit('timeout');
self.load();
}, timeout);
}

// Timeout to start loading anyways.
timeout = parseInt(this.getAttribute('timeout'), 10) || 3000;
this.timeout = setTimeout(function () {
if (self.hasLoaded) { return; }
warn('Asset loading timed out in ', timeout, 'ms');
self.emit('timeout');
self.load();
}, timeout);
}
},
disconnectedCallback () {
if (this.timeout) { clearTimeout(this.timeout); }
}

detachedCallback: {
value: function () {
if (this.timeout) { clearTimeout(this.timeout); }
}
},
load () {
super.load.call(this, null, function waitOnFilter (el) {
return el.isAssetItem && el.hasAttribute('src');
});
}
}

load: {
value: function () {
ANode.prototype.load.call(this, null, function waitOnFilter (el) {
return el.isAssetItem && el.hasAttribute('src');
});
}
}
})
});
customElements.define('a-assets', AAssets);

/**
* Preload using XHRLoader for any type of asset.
*/
registerElement('a-asset-item', {
prototype: Object.create(ANode.prototype, {
createdCallback: {
value: function () {
this.data = null;
this.isAssetItem = true;
}
},
class AAssetItem extends ANode {
constructor () {
super();
this.data = null;
this.isAssetItem = true;
}

attachedCallback: {
value: function () {
var self = this;
var src = this.getAttribute('src');
fileLoader.setResponseType(
this.getAttribute('response-type') || inferResponseType(src));
fileLoader.load(src, function handleOnLoad (response) {
self.data = response;
/*
Workaround for a Chrome bug. If another XHR is sent to the same url before the
previous one closes, the second request never finishes.
setTimeout finishes the first request and lets the logic triggered by load open
subsequent requests.
setTimeout can be removed once the fix for the bug below ships:
https://bugs.chromium.org/p/chromium/issues/detail?id=633696&q=component%3ABlink%3ENetwork%3EXHR%20&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
*/
setTimeout(function load () { ANode.prototype.load.call(self); });
}, function handleOnProgress (xhr) {
self.emit('progress', {
loadedBytes: xhr.loaded,
totalBytes: xhr.total,
xhr: xhr
});
}, function handleOnError (xhr) {
self.emit('error', {xhr: xhr});
});
}
}
})
});
connectedCallback () {
var self = this;
var src = this.getAttribute('src');
fileLoader.setResponseType(
this.getAttribute('response-type') || inferResponseType(src));
fileLoader.load(src, function handleOnLoad (response) {
self.data = response;
/*
Workaround for a Chrome bug. If another XHR is sent to the same url before the
previous one closes, the second request never finishes.
setTimeout finishes the first request and lets the logic triggered by load open
subsequent requests.
setTimeout can be removed once the fix for the bug below ships:
https://bugs.chromium.org/p/chromium/issues/detail?id=633696&q=component%3ABlink%3ENetwork%3EXHR%20&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
*/
setTimeout(function load () { ANode.prototype.load.call(self); });
}, function handleOnProgress (xhr) {
self.emit('progress', {
loadedBytes: xhr.loaded,
totalBytes: xhr.total,
xhr: xhr
});
}, function handleOnError (xhr) {
self.emit('error', {xhr: xhr});
});
}
}

customElements.define('a-asset-item', AAssetItem);

/**
* Create a Promise that resolves once the media element has finished buffering.
Expand Down
72 changes: 33 additions & 39 deletions src/core/a-cubemap.js
Original file line number Diff line number Diff line change
@@ -1,48 +1,42 @@
/* global customElements, HTMLElement */
var debug = require('../utils/debug');
var registerElement = require('./a-register-element').registerElement;

var warn = debug('core:cubemap:warn');

/**
* Cubemap element that handles validation and exposes list of URLs.
* Does not listen to updates.
*/
module.exports = registerElement('a-cubemap', {
prototype: Object.create(window.HTMLElement.prototype, {
/**
* Calculates this.srcs.
*/
attachedCallback: {
value: function () {
this.srcs = this.validate();
},
writable: window.debug
},
class ACubeMap extends HTMLElement {
/**
* Calculates this.srcs.
*/
constructor () {
super();
this.srcs = this.validate();
}

/**
* Checks for exactly six elements with [src].
* Does not check explicitly for <img>s in case user does not want
* prefetching.
*
* @returns {Array|null} - six URLs if valid, else null.
*/
validate: {
value: function () {
var elements = this.querySelectorAll('[src]');
var i;
var srcs = [];
if (elements.length === 6) {
for (i = 0; i < elements.length; i++) {
srcs.push(elements[i].getAttribute('src'));
}
return srcs;
}
// Else if there are not six elements, throw a warning.
warn(
'<a-cubemap> did not contain exactly six elements each with a ' +
'`src` attribute.');
},
writable: window.debug
/**
* Checks for exactly six elements with [src].
* Does not check explicitly for <img>s in case user does not want
* prefetching.
*
* @returns {Array|null} - six URLs if valid, else null.
*/
validate () {
var elements = this.querySelectorAll('[src]');
var i;
var srcs = [];
if (elements.length === 6) {
for (i = 0; i < elements.length; i++) {
srcs.push(elements[i].getAttribute('src'));
}
return srcs;
}
})
});
// Else if there are not six elements, throw a warning.
warn(
'<a-cubemap> did not contain exactly six elements each with a ' +
'`src` attribute.');
}
}

customElements.define('a-cubemap', ACubeMap);
Loading